mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 04:13:47 +00:00
feat: granular endpoints for rundown
This commit is contained in:
committed by
Carlos Valente
parent
e6c3322321
commit
917b182e39
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
rundownApplyDelay,
|
||||
rundownBatchPut,
|
||||
rundownDelete,
|
||||
rundownGetAll,
|
||||
rundownGetById,
|
||||
rundownGetNormalised,
|
||||
rundownGetPaginated,
|
||||
rundownPost,
|
||||
rundownPut,
|
||||
rundownReorder,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
paramsMustHaveEventId,
|
||||
rundownArrayOfIds,
|
||||
rundownBatchPutValidator,
|
||||
rundownGetPaginatedQueryParams,
|
||||
rundownPostValidator,
|
||||
rundownPutValidator,
|
||||
rundownReorderValidator,
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
@@ -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();
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
EndAction,
|
||||
isOntimeEvent,
|
||||
LogOrigin,
|
||||
MaybeNumber,
|
||||
OntimeEvent,
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -529,7 +533,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user