Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(poster): Add Hubble poster handler #16

Merged
merged 11 commits into from
Dec 29, 2024
2 changes: 1 addition & 1 deletion openapi-ts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion src/modules/handlers/screen/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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 class BasePosterScreenController extends Controller {
protected screenHandler: BasePosterScreenHandler;

constructor() {
super();
}

public async getPosters(): Promise<BasePosterResponse> {
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.filter((p) => !p.borrelMode)
};
}

public async forceUpdatePosters(@Request() req: ExpressRequest): Promise<void> {
logger.audit(req.user, 'Force fetch posters from source.');
await this.screenHandler.posterManager.fetchPosters();
this.screenHandler.forceUpdate();
}

public async getTrains(): Promise<TrainResponse[]> {
return new NsTrainsService().getTrains();
}

public async getOlympicsMedalTable() {
return new OlympicsService().getMedalTable();
}
}
25 changes: 25 additions & 0 deletions src/modules/handlers/screen/poster/base-poster-screen-handler.ts
Original file line number Diff line number Diff line change
@@ -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 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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 {TrainResponse} from "../ns-trains-service";
import GEWISPosterService, {GEWISPhotoAlbumParams} from "./gewis-poster-service";
import OlympicsService from "../olympics-service";
import {FeatureEnabled} from "../../../../server-settings";

interface GewisPosterResponse extends BasePosterResponse {
borrelMode: boolean;
}

@Route('handler/screen/gewis-poster')
@Tags('Handlers')
@FeatureEnabled('GewisPosterScreenHandler')
export class GewisPosterScreenController extends BasePosterScreenController {
protected screenHandler: GewisPosterScreenHandler;

constructor() {
super();
this.screenHandler = HandlerManager.getInstance()
.getHandlers(Screen)
.filter((h) => h.constructor.name === GewisPosterScreenHandler.name)[0] as GewisPosterScreenHandler;
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Get('')
public async getGewisPosters(@Query() alwaysReturnBorrelPosters?: boolean): Promise<GewisPosterResponse> {
if (!this.screenHandler.posterManager.posters) {
try {
await this.screenHandler.posterManager.fetchPosters();
} catch (e) {
logger.error(e);
}
}
const posters = this.screenHandler.posterManager.posters ?? [];
if (alwaysReturnBorrelPosters || this.screenHandler.borrelMode) {
return {
posters,
borrelMode: this.screenHandler.borrelMode,
};
}
return {
posters: posters.filter((p) => !p.borrelMode),
borrelMode: false,
};
}

@Security(SecurityNames.LOCAL, securityGroups.poster.privileged)
@Post('force-update')
public async forceUpdateGewisPosters(@Request() req: ExpressRequest): Promise<void> {
super.forceUpdatePosters(req);
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Get('borrel-mode')
public async getPosterBorrelMode(): Promise<BorrelModeParams> {
return { enabled: this.screenHandler.borrelMode };
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Put('borrel-mode')
public async setPosterBorrelMode(
@Request() req: ExpressRequest,
@Body() body: BorrelModeParams,
): Promise<void> {
logger.audit(
req.user,
`Set poster screen borrel mode to "${body.enabled ? 'true' : 'false'}".`,
);
this.screenHandler.setBorrelModeEnabled(body.enabled);
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Get('train-departures')
public async getTrains(): Promise<TrainResponse[]> {
return super.getTrains();
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Post('photo')
public async getPhoto(@Body() params: GEWISPhotoAlbumParams) {
return new GEWISPosterService().getPhoto(params);
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Get('olympics/medal-table')
public async getOlympicsMedalTable() {
return super.getOlympicsMedalTable();
}

@Security(SecurityNames.LOCAL, securityGroups.poster.base)
@Get('olympics/country-medals')
public async getDutchOlympicMedals() {
return new OlympicsService().getDutchMedals();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import BasePosterScreenHandler from "../base-poster-screen-handler";
import { Namespace } from "socket.io";
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);

// Check whether we need to enable/disable borrel mode
this.borrelModeInterval = setInterval(this.checkBorrelMode.bind(this), 60 * 1000);
}


public get borrelMode() {
return this.borrelModeDay !== undefined;
}

private checkBorrelMode() {
const now = new Date();

// If borrelmode is enabled, but we arrive at the next day, disable borrelmode again
if (this.borrelMode && this.borrelModeDay !== now.getDay()) {
this.borrelModeDay = undefined;
return;
}
// if borrelmode is enabled, we do not have to check whether we can enable it
if (this.borrelMode) return;

// By default, enable borrelmode on Thursdays 16:30 local time
if (
now.getDay() === 4 &&
((now.getHours() === 16 && now.getMinutes() >= 30) || now.getHours() >= 17)
) {
this.borrelModeDay = now.getDay();
}
}

public setBorrelModeEnabled(enabled: boolean) {
if (enabled) {
this.borrelModeDay = new Date().getDay();
} else {
this.borrelModeDay = undefined;
}
}
}
Original file line number Diff line number Diff line change
@@ -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[];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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<BasePosterResponse> {
return super.getPosters();
}

@Security(SecurityNames.LOCAL, securityGroups.poster.privileged)
@Post('force-update')
public async forceUpdateHubblePosters(@Request() req: ExpressRequest): Promise<void> {
super.forceUpdatePosters(req);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 2 additions & 2 deletions src/modules/handlers/screen/poster/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// 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';
export { BasePosterScreenController } from './base-poster-screen-controller';
export { default as PosterScreenHandler } from './base-poster-screen-handler';
Loading