Flag overnight blocks in the v2 schedule API

This commit is contained in:
NeilClack
2026-09-11 18:24:51 -05:00
parent 750a9286c9
commit 177b7bb13a
4 changed files with 55 additions and 15 deletions
+8 -2
View File
@@ -1,4 +1,4 @@
import { toHm } from '$lib/schedule-utils';
import { blockDuration, isOvernight, toHm } from '$lib/schedule-utils';
// Canonical solfeggio id <- Hz, mirroring the kiosk's `frequencyIdForHz()`
// map (radiantwave-rebuild src/audio/audio_downloader.cpp). v2 must emit these
@@ -147,6 +147,8 @@ export type V2ScheduleBlock = {
startMinutes: number;
endTime: string;
endMinutes: number;
overnight: boolean;
durationMinutes: number;
proclamationIds: string[];
};
@@ -156,7 +158,9 @@ export type V2Schedule = {
};
/** Build a schedule Block for the kiosk. Only the selected Proclamation IDs are
* returned; the kiosk fetches proclamation content separately. */
* returned; the kiosk fetches proclamation content separately. `overnight`
* marks a range that crosses midnight (end clock time before start), and
* `durationMinutes` counts the wrap as the next day. */
export function serializeScheduleBlock(
block: { id: string; startMinutes: number; endMinutes: number },
proclamationIds: string[]
@@ -167,6 +171,8 @@ export function serializeScheduleBlock(
startMinutes: block.startMinutes,
endTime: toHm(block.endMinutes),
endMinutes: block.endMinutes,
overnight: isOvernight(block),
durationMinutes: blockDuration(block),
proclamationIds
};
}
+22 -9
View File
@@ -86,7 +86,7 @@ describe('GET /api/v2/schedule auth', () => {
});
describe('GET /api/v2/schedule success', () => {
it('returns only proclamation IDs even when the key is inactive', async () => {
it('returns only proclamation IDs, marking an overnight block, even when the key is inactive', async () => {
licenseKeyFindUnique.mockResolvedValue({
key: KEY,
userId: 'user-1',
@@ -97,8 +97,8 @@ describe('GET /api/v2/schedule success', () => {
blockFindMany.mockResolvedValue([
{
id: 'block-1',
startMinutes: 360,
endMinutes: 480,
startMinutes: 21 * 60,
endMinutes: 9 * 60,
proclamations: [{ proclamation: { id: 'p1' } }, { proclamation: { id: 'p2' } }]
}
]);
@@ -111,10 +111,12 @@ describe('GET /api/v2/schedule success', () => {
blocks: [
{
id: 'block-1',
startTime: '06:00',
startMinutes: 360,
endTime: '08:00',
endMinutes: 480,
startTime: '21:00',
startMinutes: 1260,
endTime: '09:00',
endMinutes: 540,
overnight: true,
durationMinutes: 720,
proclamationIds: ['p1', 'p2']
}
]
@@ -134,9 +136,20 @@ describe('GET /api/v2/schedule success', () => {
]);
const response = await call({ email: 'jane@example.com', licenseKey: KEY });
const body = (await response.json()) as { blocks: { proclamationIds: string[] }[] };
const body = (await response.json()) as {
blocks: { proclamationIds: string[]; overnight: boolean; durationMinutes: number }[];
};
expect(body.blocks[0].proclamationIds).toEqual(['p1']);
expect(body.blocks[0]).toEqual({
id: 'block-1',
startTime: '00:00',
startMinutes: 0,
endTime: '01:00',
endMinutes: 60,
overnight: false,
durationMinutes: 60,
proclamationIds: ['p1']
});
});
it('accepts the legacy `key` query alias and an email-only license', async () => {