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

Rundown api #1129

Merged
merged 2 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 53 additions & 8 deletions apps/server/src/api-data/rundown/rundown.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ErrorResponse, MessageResponse, OntimeRundown, OntimeRundownEntry, RundownCached } from 'ontime-types';
import { ErrorResponse, MessageResponse, OntimeRundownEntry, RundownCached, RundownPaginated } from 'ontime-types';
import { getErrorMessage } from 'ontime-utils';

import { Request, Response } from 'express';

Expand All @@ -13,19 +14,63 @@ import {
reorderEvent,
swapEvents,
} from '../../services/rundown-service/RundownService.js';
import { getNormalisedRundown, getRundown } from '../../services/rundown-service/rundownUtils.js';
import { getErrorMessage } from 'ontime-utils';

export async function rundownGetAll(_req: Request, res: Response<OntimeRundown>) {
const rundown = getRundown();
res.json(rundown);
}
import {
getEventWithId,
getNormalisedRundown,
getPaginated,
getRundown,
} from '../../services/rundown-service/rundownUtils.js';

export async function rundownGetNormalised(_req: Request, res: Response<RundownCached>) {
const cachedRundown = getNormalisedRundown();
res.json(cachedRundown);
}

export async function rundownGetById(req: Request, res: Response<OntimeRundownEntry | ErrorResponse>) {
const { eventId } = req.params;

try {
const event = getEventWithId(eventId);

if (!event) {
res.status(404).send({ message: 'Event not found' });
return;
}
res.status(200).json(event);
} catch (error) {
const message = getErrorMessage(error);
res.status(500).json({ message });
}
}

export async function rundownGetPaginated(req: Request, res: Response<RundownPaginated | ErrorResponse>) {
const { limit, offset } = req.query;

if (limit == null && offset == null) {
return res.json({
rundown: getRundown(),
total: getRundown().length,
});
}

try {
let parsedOffset = Number(offset);
if (Number.isNaN(parsedOffset)) {
parsedOffset = 0;
}
let parsedLimit = Number(limit);
if (Number.isNaN(parsedLimit)) {
parsedLimit = Infinity;
}
const paginatedRundown = getPaginated(parsedOffset, parsedLimit);

res.status(200).json(paginatedRundown);
} catch (error) {
const message = getErrorMessage(error);
res.status(400).json({ message });
}
}

export async function rundownPost(req: Request, res: Response<OntimeRundownEntry | ErrorResponse>) {
if (failEmptyObjects(req.body, res)) {
return;
Expand Down
7 changes: 5 additions & 2 deletions apps/server/src/api-data/rundown/rundown.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
rundownApplyDelay,
rundownBatchPut,
rundownDelete,
rundownGetAll,
rundownGetById,
rundownGetNormalised,
rundownGetPaginated,
rundownPost,
rundownPut,
rundownReorder,
Expand All @@ -16,6 +17,7 @@ import {
paramsMustHaveEventId,
rundownArrayOfIds,
rundownBatchPutValidator,
rundownGetPaginatedQueryParams,
rundownPostValidator,
rundownPutValidator,
rundownReorderValidator,
Expand All @@ -24,8 +26,9 @@ import {

export const router = express.Router();

router.get('/', rundownGetAll); // not used in Ontime frontend
router.get('/', rundownGetPaginatedQueryParams, rundownGetPaginated); // not used in Ontime frontend
router.get('/normalised', rundownGetNormalised);
router.get('/:eventId', paramsMustHaveEventId, rundownGetById); // not used in Ontime frontend

router.post('/', rundownPostValidator, rundownPost);

Expand Down
13 changes: 12 additions & 1 deletion apps/server/src/api-data/rundown/rundown.validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { body, param, validationResult } from 'express-validator';
import { body, param, query, validationResult } from 'express-validator';
import { Request, Response, NextFunction } from 'express';

export const rundownPostValidator = [
Expand Down Expand Up @@ -75,3 +75,14 @@ export const rundownArrayOfIds = [
next();
},
];

export const rundownGetPaginatedQueryParams = [
query('offset').isNumeric().optional(),
query('limit').isNumeric().optional(),

(req: Request, res: Response, next: NextFunction) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
next();
},
];
2 changes: 1 addition & 1 deletion apps/server/src/services/rundown-service/RundownService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ function notifyChanges(options: { timer?: boolean | string[]; external?: boolean

if (options.external) {
// advice socket subscribers of change
sendRefetch();
sendRefetch(Array.isArray(options.timer) ? options.timer : undefined);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { OntimeRundown } from 'ontime-types';
import { getPaginated } from '../rundownUtils.js';

describe('getPaginated', () => {
// mock cache so we dont run data functions
beforeAll(() => {
vi.mock('../rundownCache.js', () => ({}));
});

// @ts-expect-error -- we know this is not correct, but good enough for the test
const getData = () => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as OntimeRundown;

it('should return the correct paginated rundown', () => {
const offset = 0;
const limit = 1;
const result = getPaginated(offset, limit, getData);

expect(result.rundown).toHaveLength(1);
expect(result.total).toBe(10);
});

it('should handle overflows', () => {
const offset = 0;
const limit = 20;
const result = getPaginated(offset, limit, getData);

expect(result.rundown).toHaveLength(10);
expect(result.total).toBe(10);
});

it('should handle out of range', () => {
const offset = 11;
const limit = Infinity;
const result = getPaginated(offset, limit, getData);

expect(result.rundown).toHaveLength(0);
expect(result.total).toBe(10);
});
});
24 changes: 20 additions & 4 deletions apps/server/src/services/rundown-service/rundownUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OntimeEvent, OntimeRundown, isOntimeEvent, RundownCached } from 'ontime-types';
import { OntimeEvent, OntimeRundown, isOntimeEvent, RundownCached, OntimeRundownEntry } from 'ontime-types';

import * as cache from './rundownCache.js';

Expand Down Expand Up @@ -53,9 +53,9 @@ export function getEventAtIndex(eventIndex: number): OntimeEvent | undefined {
* @param {string} eventId
* @return {object | undefined}
*/
export function getEventWithId(eventId: string): OntimeEvent | undefined {
const timedEvents = getTimedEvents();
return timedEvents.find((event) => event.id === eventId);
export function getEventWithId(eventId: string): OntimeRundownEntry | undefined {
const rundown = getRundown();
return rundown.find((event) => event.id === eventId);
}

/**
Expand Down Expand Up @@ -117,3 +117,19 @@ export function findNext(currentEventId?: string): OntimeEvent | null {
const nextEvent = timedEvents.at(newIndex);
return nextEvent ?? null;
}

/**
* Returns a paginated rundown
* Exposes a getter function for the rundown for testing
*/
export function getPaginated(
offset: number,
limit: number,
source = getRundown,
): { rundown: OntimeRundownEntry[]; total: number } {
const rundown = source();
return {
rundown: rundown.slice(Math.min(offset, rundown.length), Math.min(offset + limit, rundown.length)),
total: rundown.length,
};
}
10 changes: 7 additions & 3 deletions apps/server/src/services/runtime-service/RuntimeService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
EndAction,
isOntimeEvent,
LogOrigin,
MaybeNumber,
OntimeEvent,
Expand Down Expand Up @@ -225,6 +226,9 @@ class RuntimeService {
}
// load stuff again, but keep running if our events still exist
const eventNow = getEventWithId(state.eventNow.id);
if (!isOntimeEvent(eventNow)) {
return;
}
const onlyChangedNow = affectedIds?.length === 1 && affectedIds.at(0) === eventNow.id;
if (onlyChangedNow) {
runtimeState.reload(eventNow);
Expand Down Expand Up @@ -272,7 +276,7 @@ class RuntimeService {
*/
startById(eventId: string): boolean {
const event = getEventWithId(eventId);
if (!event) {
if (!event || !isOntimeEvent(event)) {
return false;
}
const loaded = this.loadEvent(event);
Expand Down Expand Up @@ -323,7 +327,7 @@ class RuntimeService {
*/
loadById(eventId: string): boolean {
const event = getEventWithId(eventId);
if (!event) {
if (!event || !isOntimeEvent(event)) {
return false;
}
return this.loadEvent(event);
Expand Down Expand Up @@ -523,7 +527,7 @@ class RuntimeService {
// the db would have to change for the event not to exist
// we do not kow the reason for the crash, so we check anyway
const event = getEventWithId(selectedEventId);
if (!event) {
if (!event || !isOntimeEvent(event)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { OSCSettings } from '../../definitions/core/OscSettings.type.js';
import type { OntimeRundown } from '../../definitions/core/Rundown.type.js';

export type NetworkInterface = {
name: string;
Expand Down Expand Up @@ -32,3 +33,8 @@ export type MessageResponse = {
export type ErrorResponse = MessageResponse;

export type AuthenticationStatus = 'authenticated' | 'not_authenticated' | 'pending';

export type RundownPaginated = {
rundown: OntimeRundown;
total: number;
};
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type {
ErrorResponse,
ProjectFileListResponse,
MessageResponse,
RundownPaginated,
} from './api/ontime-controller/BackendResponse.type.js';
export type { RundownCached, NormalisedRundown } from './api/rundown-controller/BackendResponse.type.js';

Expand Down