diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts index 556051f..b1b3ba8 100644 --- a/openapi-ts.config.ts +++ b/openapi-ts.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from '@hey-api/openapi-ts'; export default defineConfig({ client: '@hey-api/client-fetch', input: './build/swagger.json', - output: `../narrowcasting-client/src/api/`, + output: `../aurora-client/src/api/`, plugins: [ '@hey-api/schemas', '@hey-api/services', diff --git a/src/modules/handlers/screen/index.ts b/src/modules/handlers/screen/index.ts index 0ff9fd1..0e08312 100644 --- a/src/modules/handlers/screen/index.ts +++ b/src/modules/handlers/screen/index.ts @@ -1,6 +1,7 @@ export { default as CurrentlyPlayingTrackHandler } from './curently-playing-track-handler'; export { default as CenturionScreenHandler } from './centurion-screen-handler'; -export { default as PosterScreenHandler } from './poster/poster-screen-handler'; +export { default as GewisPosterScreenHandler } from './poster/gewis/gewis-poster-screen-handler'; +export { default as HubblePosterScreenHandler } from './poster/hubble/hubble-poster-screen-handler'; export { default as RoomResponsibleLegacyHandler } from './room-responsible-legacy-handler'; export { default as StageEffectsHandler } from './stage-effects-handler'; export { default as TimeTrailRaceScreenHandler } from './time-trail-race-screen-handler'; diff --git a/src/modules/handlers/screen/poster/base-poster-screen-controller.ts b/src/modules/handlers/screen/poster/base-poster-screen-controller.ts new file mode 100644 index 0000000..2cf14f5 --- /dev/null +++ b/src/modules/handlers/screen/poster/base-poster-screen-controller.ts @@ -0,0 +1,52 @@ +import { Controller } from '@tsoa/runtime'; +import { Request } from 'tsoa'; +import { Request as ExpressRequest } from 'express'; +import BasePosterScreenHandler from './base-poster-screen-handler'; +import { Poster } from './poster'; +import logger from '../../../../logger'; +import OlympicsService from './olympics-service'; +import NsTrainsService, { TrainResponse } from './ns-trains-service'; + +export interface BorrelModeParams { + enabled: boolean; +} + +export interface BasePosterResponse { + posters: Poster[]; +} + +export abstract class BasePosterScreenController extends Controller { + protected screenHandler: BasePosterScreenHandler; + + protected constructor() { + super(); + } + + public async getPosters(): Promise { + if (!this.screenHandler.posterManager.posters) { + try { + await this.screenHandler.posterManager.fetchPosters(); + } catch (e) { + logger.error(e); + } + } + const posters = this.screenHandler.posterManager.posters ?? []; + return { + posters: posters, + }; + } + + public async forceUpdatePosters(@Request() req: ExpressRequest): Promise { + logger.audit(req.user, 'Force fetch posters from source.'); + await this.screenHandler.posterManager.fetchPosters(); + this.screenHandler.forceUpdate(); + } + + public async getTrains(): Promise { + return new NsTrainsService().getTrains(); + } + + public async getOlympicsMedalTable() { + return new OlympicsService().getMedalTable(); + } +} diff --git a/src/modules/handlers/screen/poster/base-poster-screen-handler.ts b/src/modules/handlers/screen/poster/base-poster-screen-handler.ts new file mode 100644 index 0000000..8b4939b --- /dev/null +++ b/src/modules/handlers/screen/poster/base-poster-screen-handler.ts @@ -0,0 +1,25 @@ +import { Namespace } from 'socket.io'; +import BaseScreenHandler from '../../base-screen-handler'; +import { TrackChangeEvent } from '../../../events/music-emitter-events'; +import { PosterManager } from './poster-manager'; +import { TrelloPosterManager } from './trello/trello-poster-manager'; + +export default abstract class BasePosterScreenHandler extends BaseScreenHandler { + public posterManager: PosterManager; + + constructor(socket: Namespace) { + super(socket); + this.posterManager = new TrelloPosterManager(); + } + + forceUpdate(): void { + this.sendEvent('update_posters'); + } + + // Do nothing + beat(): void {} + + changeTrack(event: TrackChangeEvent[]): void { + this.sendEvent('change_track', event); + } +} diff --git a/src/modules/handlers/screen/poster/poster-screen-controller.ts b/src/modules/handlers/screen/poster/gewis/gewis-poster-screen-controller.ts similarity index 54% rename from src/modules/handlers/screen/poster/poster-screen-controller.ts rename to src/modules/handlers/screen/poster/gewis/gewis-poster-screen-controller.ts index 1bdcd36..22794e2 100644 --- a/src/modules/handlers/screen/poster/poster-screen-controller.ts +++ b/src/modules/handlers/screen/poster/gewis/gewis-poster-screen-controller.ts @@ -1,68 +1,62 @@ -import { Controller } from '@tsoa/runtime'; +import { + BasePosterResponse, + BasePosterScreenController, + BorrelModeParams, +} from '../base-poster-screen-controller'; +import HandlerManager from '../../../../root/handler-manager'; +import { Screen } from '../../../../root/entities'; +import { GewisPosterScreenHandler } from '../../index'; import { Body, Get, Post, Put, Query, Request, Route, Security, Tags } from 'tsoa'; +import { SecurityNames } from '../../../../../helpers/security'; +import { securityGroups } from '../../../../../helpers/security-groups'; +import logger from '../../../../../logger'; import { Request as ExpressRequest } from 'express'; -import PosterScreenHandler from './poster-screen-handler'; -// eslint-disable-next-line import/no-cycle -- TODO fix cyclic dependency -import HandlerManager from '../../../root/handler-manager'; -import { Screen } from '../../../root/entities'; -import { Poster } from './poster'; -import { SecurityNames } from '../../../../helpers/security'; -import logger from '../../../../logger'; -import NsTrainsService, { TrainResponse } from './ns-trains-service'; +import { TrainResponse } from '../ns-trains-service'; import GEWISPosterService, { GEWISPhotoAlbumParams } from './gewis-poster-service'; -import OlympicsService from './olympics-service'; -import { securityGroups } from '../../../../helpers/security-groups'; +import OlympicsService from '../olympics-service'; +import { FeatureEnabled } from '../../../../server-settings'; -interface BorrelModeParams { - enabled: boolean; -} - -interface PosterResponse { - posters: Poster[]; +interface GewisPosterResponse extends BasePosterResponse { borrelMode: boolean; } -@Route('handler/screen/poster') +@Route('handler/screen/gewis-poster') @Tags('Handlers') -export class PosterScreenController extends Controller { - private screenHandler: PosterScreenHandler; +@FeatureEnabled('GewisPosterScreenHandler') +export class GewisPosterScreenController extends BasePosterScreenController { + protected screenHandler: GewisPosterScreenHandler; constructor() { super(); this.screenHandler = HandlerManager.getInstance() .getHandlers(Screen) - .filter((h) => h.constructor.name === PosterScreenHandler.name)[0] as PosterScreenHandler; + .filter( + (h) => h.constructor.name === GewisPosterScreenHandler.name, + )[0] as GewisPosterScreenHandler; } @Security(SecurityNames.LOCAL, securityGroups.poster.base) @Get('') - public async getPosters(@Query() alwaysReturnBorrelPosters?: boolean): Promise { - if (!this.screenHandler.posterManager.posters) { - try { - await this.screenHandler.posterManager.fetchPosters(); - } catch (e) { - logger.error(e); - } - } - const posters = this.screenHandler.posterManager.posters ?? []; + public async getGewisPosters( + @Query() alwaysReturnBorrelPosters?: boolean, + ): Promise { + const postersRes = await super.getPosters(); if (alwaysReturnBorrelPosters || this.screenHandler.borrelMode) { return { - posters, + posters: postersRes.posters, borrelMode: this.screenHandler.borrelMode, }; } return { - posters: posters.filter((p) => !p.borrelMode), + posters: postersRes.posters.filter((p) => !p.borrelMode), borrelMode: false, }; } @Security(SecurityNames.LOCAL, securityGroups.poster.privileged) @Post('force-update') - public async forceUpdatePosters(@Request() req: ExpressRequest): Promise { - logger.audit(req.user, 'Force fetch posters from source.'); - await this.screenHandler.posterManager.fetchPosters(); - this.screenHandler.forceUpdate(); + public async forceUpdateGewisPosters(@Request() req: ExpressRequest): Promise { + super.forceUpdatePosters(req); } @Security(SecurityNames.LOCAL, securityGroups.poster.base) @@ -87,7 +81,7 @@ export class PosterScreenController extends Controller { @Security(SecurityNames.LOCAL, securityGroups.poster.base) @Get('train-departures') public async getTrains(): Promise { - return new NsTrainsService().getTrains(); + return super.getTrains(); } @Security(SecurityNames.LOCAL, securityGroups.poster.base) @@ -99,7 +93,7 @@ export class PosterScreenController extends Controller { @Security(SecurityNames.LOCAL, securityGroups.poster.base) @Get('olympics/medal-table') public async getOlympicsMedalTable() { - return new OlympicsService().getMedalTable(); + return super.getOlympicsMedalTable(); } @Security(SecurityNames.LOCAL, securityGroups.poster.base) diff --git a/src/modules/handlers/screen/poster/poster-screen-handler.ts b/src/modules/handlers/screen/poster/gewis/gewis-poster-screen-handler.ts similarity index 66% rename from src/modules/handlers/screen/poster/poster-screen-handler.ts rename to src/modules/handlers/screen/poster/gewis/gewis-poster-screen-handler.ts index 459b3a2..8cf569b 100644 --- a/src/modules/handlers/screen/poster/poster-screen-handler.ts +++ b/src/modules/handlers/screen/poster/gewis/gewis-poster-screen-handler.ts @@ -1,19 +1,15 @@ +import BasePosterScreenHandler from '../base-poster-screen-handler'; import { Namespace } from 'socket.io'; -import BaseScreenHandler from '../../base-screen-handler'; -import { TrackChangeEvent } from '../../../events/music-emitter-events'; -import { PosterManager } from './poster-manager'; -import { TrelloPosterManager } from './trello/trello-poster-manager'; - -export default class PosterScreenHandler extends BaseScreenHandler { - public posterManager: PosterManager; +import { FeatureEnabled } from '../../../../server-settings'; +@FeatureEnabled('GewisPosterScreenHandler') +export default class GewisPosterScreenHandler extends BasePosterScreenHandler { private borrelModeDay: number | undefined; public borrelModeInterval: NodeJS.Timeout; constructor(socket: Namespace) { super(socket); - this.posterManager = new TrelloPosterManager(); // Check whether we need to enable/disable borrel mode this.borrelModeInterval = setInterval(this.checkBorrelMode.bind(this), 60 * 1000); @@ -50,15 +46,4 @@ export default class PosterScreenHandler extends BaseScreenHandler { this.borrelModeDay = undefined; } } - - forceUpdate(): void { - this.sendEvent('update_posters'); - } - - // Do nothing - beat(): void {} - - changeTrack(event: TrackChangeEvent[]): void { - this.sendEvent('change_track', event); - } } diff --git a/src/modules/handlers/screen/poster/gewis-poster-service.ts b/src/modules/handlers/screen/poster/gewis/gewis-poster-service.ts similarity index 93% rename from src/modules/handlers/screen/poster/gewis-poster-service.ts rename to src/modules/handlers/screen/poster/gewis/gewis-poster-service.ts index 5cc3644..b868744 100644 --- a/src/modules/handlers/screen/poster/gewis-poster-service.ts +++ b/src/modules/handlers/screen/poster/gewis/gewis-poster-service.ts @@ -1,5 +1,5 @@ import axios, { AxiosError } from 'axios'; -import { HttpApiException } from '../../../../helpers/custom-error'; +import { HttpApiException } from '../../../../../helpers/custom-error'; export interface GEWISPhotoAlbumParams { albumIds: number[]; diff --git a/src/modules/handlers/screen/poster/hubble/hubble-poster-screen-controller.ts b/src/modules/handlers/screen/poster/hubble/hubble-poster-screen-controller.ts new file mode 100644 index 0000000..e3bc72d --- /dev/null +++ b/src/modules/handlers/screen/poster/hubble/hubble-poster-screen-controller.ts @@ -0,0 +1,36 @@ +import { BasePosterResponse, BasePosterScreenController } from '../base-poster-screen-controller'; +import HandlerManager from '../../../../root/handler-manager'; +import { Screen } from '../../../../root/entities'; +import BasePosterScreenHandler from '../base-poster-screen-handler'; +import { HubblePosterScreenHandler } from '../../index'; +import { Get, Post, Request, Route, Security, Tags } from 'tsoa'; +import { SecurityNames } from '../../../../../helpers/security'; +import { securityGroups } from '../../../../../helpers/security-groups'; +import { Request as ExpressRequest } from 'express'; +import { FeatureEnabled } from '../../../../server-settings'; + +@Route('handler/screen/hubble-poster') +@Tags('Handlers') +@FeatureEnabled('HubblePosterScreenHandler') +export class HubblePosterScreenController extends BasePosterScreenController { + constructor() { + super(); + this.screenHandler = HandlerManager.getInstance() + .getHandlers(Screen) + .filter( + (h) => h.constructor.name === HubblePosterScreenHandler.name, + )[0] as BasePosterScreenHandler; + } + + @Security(SecurityNames.LOCAL, securityGroups.poster.base) + @Get('') + public async getHubblePosters(): Promise { + return super.getPosters(); + } + + @Security(SecurityNames.LOCAL, securityGroups.poster.privileged) + @Post('force-update') + public async forceUpdateHubblePosters(@Request() req: ExpressRequest): Promise { + super.forceUpdatePosters(req); + } +} diff --git a/src/modules/handlers/screen/poster/hubble/hubble-poster-screen-handler.ts b/src/modules/handlers/screen/poster/hubble/hubble-poster-screen-handler.ts new file mode 100644 index 0000000..04b2fe3 --- /dev/null +++ b/src/modules/handlers/screen/poster/hubble/hubble-poster-screen-handler.ts @@ -0,0 +1,10 @@ +import BasePosterScreenHandler from '../base-poster-screen-handler'; +import { Namespace } from 'socket.io'; +import { FeatureEnabled } from '../../../../server-settings'; + +@FeatureEnabled('HubblePosterScreenHandler') +export default class HubblePosterScreenHandler extends BasePosterScreenHandler { + constructor(socket: Namespace) { + super(socket); + } +} diff --git a/src/modules/handlers/screen/poster/index.ts b/src/modules/handlers/screen/poster/index.ts deleted file mode 100644 index a6443b5..0000000 --- a/src/modules/handlers/screen/poster/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -// eslint-disable-next-line import/no-cycle -- TODO fix cyclic dependency -export { PosterScreenController } from './poster-screen-controller'; -export { default as PosterScreenHandler } from './poster-screen-handler'; diff --git a/src/modules/handlers/screen/poster/poster.ts b/src/modules/handlers/screen/poster/poster.ts index ff25d2f..363a02b 100644 --- a/src/modules/handlers/screen/poster/poster.ts +++ b/src/modules/handlers/screen/poster/poster.ts @@ -29,7 +29,8 @@ export interface BasePoster { timeout: number; footer: FooterSize; /** Whether this poster should only be shown when in BorrelMode */ - borrelMode: boolean; + borrelMode?: boolean; + color?: string; } export type LocalPosterType = diff --git a/src/modules/handlers/screen/poster/trello/trello-poster-manager.ts b/src/modules/handlers/screen/poster/trello/trello-poster-manager.ts index 0fe06e6..98a829c 100644 --- a/src/modules/handlers/screen/poster/trello/trello-poster-manager.ts +++ b/src/modules/handlers/screen/poster/trello/trello-poster-manager.ts @@ -119,7 +119,11 @@ export class TrelloPosterManager extends PosterManager { const labels = card.labels?.map((l) => l.name ?? '') ?? []; const hideBorder = labels.includes('HIDE_BORDER'); - const footers = labels.filter((l) => !['HIDE_BORDER', 'BorrelMode'].includes(l)); + const footers = labels.filter( + (l) => !['HIDE_BORDER', 'BorrelMode'].includes(l) && !l.startsWith('#'), + ); + + let color = labels.find((l) => l.startsWith('#')); return { id: card.id ?? randomUUID(), @@ -128,10 +132,11 @@ export class TrelloPosterManager extends PosterManager { // If there is a due date present, set the due date due: card.due ? new Date(card.due) : undefined, // If there are labels, set the label of this poster to be the first label of the card - label: footers.length > 0 ? labels[0] : '', + label: footers.length > 0 ? footers[0] : '', // If the card has a HIDE_LABEL label, set the footer size to minimal footer: hideBorder ? FooterSize.MINIMAL : FooterSize.FULL, borrelMode, + color, }; } diff --git a/src/modules/handlers/screen/screen-handler-settings.ts b/src/modules/handlers/screen/screen-handler-settings.ts index 7d9439a..7fd49ff 100644 --- a/src/modules/handlers/screen/screen-handler-settings.ts +++ b/src/modules/handlers/screen/screen-handler-settings.ts @@ -1,7 +1,11 @@ export interface ScreenHandlerSettings { RoomResponsibleLegacyScreenURL: string; + HubblePosterScreenHandler: boolean; + GewisPosterScreenHandler: boolean; } export const ScreenHandlerSettingsDefaults: ScreenHandlerSettings = { RoomResponsibleLegacyScreenURL: '', + HubblePosterScreenHandler: false, + GewisPosterScreenHandler: false, }; diff --git a/src/modules/root/handler-factory.ts b/src/modules/root/handler-factory.ts index 91e8358..18dd0cb 100644 --- a/src/modules/root/handler-factory.ts +++ b/src/modules/root/handler-factory.ts @@ -16,7 +16,8 @@ import BaseScreenHandler from '../handlers/base-screen-handler'; import { CenturionScreenHandler, CurrentlyPlayingTrackHandler, - PosterScreenHandler, + GewisPosterScreenHandler, + HubblePosterScreenHandler, RoomResponsibleLegacyHandler, StageEffectsHandler, TimeTrailRaceScreenHandler, @@ -80,7 +81,8 @@ export default class HandlerFactory { () => new CurrentlyPlayingTrackHandler(socket), ), this.createHandler(CenturionScreenHandler, () => new CenturionScreenHandler(socket)), - this.createHandler(PosterScreenHandler, () => new PosterScreenHandler(socket)), + this.createHandler(GewisPosterScreenHandler, () => new GewisPosterScreenHandler(socket)), + this.createHandler(HubblePosterScreenHandler, () => new HubblePosterScreenHandler(socket)), this.createHandler(StageEffectsHandler, () => new StageEffectsHandler(socket)), this.createHandler(TimeTrailRaceScreenHandler, () => new TimeTrailRaceScreenHandler(socket)), this.createHandler( diff --git a/src/modules/root/handler-manager.ts b/src/modules/root/handler-manager.ts index d9049a7..51c3199 100644 --- a/src/modules/root/handler-manager.ts +++ b/src/modules/root/handler-manager.ts @@ -14,7 +14,6 @@ import DevelopEffectsHandler from '../handlers/lights/develop-effects-handler'; import { BeatEvent, TrackChangeEvent } from '../events/music-emitter-events'; import { CurrentlyPlayingTrackHandler, CenturionScreenHandler } from '../handlers/screen'; // eslint-disable-next-line import/no-cycle -- TODO fix cyclic dependency -import { PosterScreenHandler } from '../handlers/screen/poster'; import { ScenesHandler } from '../handlers/lights/scenes-handler'; import EffectSequenceHandler from '../handlers/lights/effect-sequence-handler'; import { MusicEmitter } from '../events'; diff --git a/src/seed/seed.ts b/src/seed/seed.ts index f8f4ccb..765345d 100644 --- a/src/seed/seed.ts +++ b/src/seed/seed.ts @@ -13,7 +13,7 @@ import { LightsScene, LightsSceneEffect } from '../modules/lights/entities/scene import { SearchLightCreateParams } from '../modules/lights/effects/movement/search-light'; import LightsWheelColorChannelValue from '../modules/lights/entities/lights-wheel-color-channel-value'; import LightsWheelGoboChannelValue from '../modules/lights/entities/lights-wheel-gobo-channel-value'; -import { PosterScreenHandler } from '../modules/handlers/screen/poster'; +import GewisPosterScreenHandler from '../modules/handlers/screen/poster/gewis/gewis-poster-screen-handler'; import SimpleAudioHandler from '../modules/handlers/audio/simple-audio-handler'; import LightsWheelRotateChannelValue from '../modules/lights/entities/lights-wheel-rotate-channel-value'; import { FixedPositionCreateParams } from '../modules/lights/effects/movement/fixed-position'; @@ -32,15 +32,15 @@ export default async function seedDatabase() { const rootScreenService = new RootScreenService(); await rootScreenService.createScreen({ name: 'PCGEWISB-links', - defaultHandler: PosterScreenHandler.name, + defaultHandler: GewisPosterScreenHandler.name, }); await rootScreenService.createScreen({ name: 'PCGEWISB-rechts', - defaultHandler: PosterScreenHandler.name, + defaultHandler: GewisPosterScreenHandler.name, }); await rootScreenService.createScreen({ name: 'PCGEWISINFO', - defaultHandler: PosterScreenHandler.name, + defaultHandler: GewisPosterScreenHandler.name, }); const rootLightsService = new RootLightsService();