Flag overnight blocks in the v2 schedule API
This commit is contained in:
+4
-2
@@ -107,8 +107,10 @@ Authenticates with the paired `email` + `licenseKey` query parameters and
|
|||||||
validates only that the key belongs to the account (not that the license is
|
validates only that the key belongs to the account (not that the license is
|
||||||
active/entitled). Returns the user's Blocks with start/end times and the IDs of
|
active/entitled). Returns the user's Blocks with start/end times and the IDs of
|
||||||
the selected Proclamations (content is fetched separately via
|
the selected Proclamations (content is fetched separately via
|
||||||
`/api/v2/proclamations`). Rate limited to 10 requests per minute per IP.
|
`/api/v2/proclamations`). Overnight blocks (end clock time before start) are
|
||||||
Failure messages follow the existing v2 shape:
|
supported and flagged with `overnight: true` plus a wrapping `durationMinutes`.
|
||||||
|
Rate limited to 10 requests per minute per IP. Failure messages follow the
|
||||||
|
existing v2 shape:
|
||||||
`not-authorized` or `this key doesn't appear to belong to you`.
|
`not-authorized` or `this key doesn't appear to belong to you`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+21
-2
@@ -245,7 +245,9 @@ paths:
|
|||||||
pairing.
|
pairing.
|
||||||
|
|
||||||
Returns every Block with its start/end times and the ordered list of
|
Returns every Block with its start/end times and the ordered list of
|
||||||
selected Proclamation IDs. Proclamation content is not included; the
|
selected Proclamation IDs. Overnight blocks (an end clock time before
|
||||||
|
the start) are supported and flagged with `overnight: true` and a
|
||||||
|
wrapping `durationMinutes`. Proclamation content is not included; the
|
||||||
kiosk fetches it separately via `/api/v2/proclamations`.
|
kiosk fetches it separately via `/api/v2/proclamations`.
|
||||||
operationId: getSchedule
|
operationId: getSchedule
|
||||||
parameters:
|
parameters:
|
||||||
@@ -498,7 +500,17 @@ components:
|
|||||||
format: email
|
format: email
|
||||||
ScheduleBlock:
|
ScheduleBlock:
|
||||||
type: object
|
type: object
|
||||||
required: [id, startTime, startMinutes, endTime, endMinutes, proclamationIds]
|
required:
|
||||||
|
[
|
||||||
|
id,
|
||||||
|
startTime,
|
||||||
|
startMinutes,
|
||||||
|
endTime,
|
||||||
|
endMinutes,
|
||||||
|
overnight,
|
||||||
|
durationMinutes,
|
||||||
|
proclamationIds
|
||||||
|
]
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
@@ -521,6 +533,13 @@ components:
|
|||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 1440
|
maximum: 1440
|
||||||
description: Minutes from midnight.
|
description: Minutes from midnight.
|
||||||
|
overnight:
|
||||||
|
type: boolean
|
||||||
|
description: True when the end clock time is at or before the start, i.e. the block crosses midnight.
|
||||||
|
durationMinutes:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
description: Block length in minutes. An overnight block counts the wrap past midnight as the next day.
|
||||||
proclamationIds:
|
proclamationIds:
|
||||||
type: array
|
type: array
|
||||||
description: IDs of the selected Proclamations, in play order.
|
description: IDs of the selected Proclamations, in play order.
|
||||||
|
|||||||
@@ -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()`
|
// Canonical solfeggio id <- Hz, mirroring the kiosk's `frequencyIdForHz()`
|
||||||
// map (radiantwave-rebuild src/audio/audio_downloader.cpp). v2 must emit these
|
// map (radiantwave-rebuild src/audio/audio_downloader.cpp). v2 must emit these
|
||||||
@@ -147,6 +147,8 @@ export type V2ScheduleBlock = {
|
|||||||
startMinutes: number;
|
startMinutes: number;
|
||||||
endTime: string;
|
endTime: string;
|
||||||
endMinutes: number;
|
endMinutes: number;
|
||||||
|
overnight: boolean;
|
||||||
|
durationMinutes: number;
|
||||||
proclamationIds: string[];
|
proclamationIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,7 +158,9 @@ export type V2Schedule = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Build a schedule Block for the kiosk. Only the selected Proclamation IDs are
|
/** 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(
|
export function serializeScheduleBlock(
|
||||||
block: { id: string; startMinutes: number; endMinutes: number },
|
block: { id: string; startMinutes: number; endMinutes: number },
|
||||||
proclamationIds: string[]
|
proclamationIds: string[]
|
||||||
@@ -167,6 +171,8 @@ export function serializeScheduleBlock(
|
|||||||
startMinutes: block.startMinutes,
|
startMinutes: block.startMinutes,
|
||||||
endTime: toHm(block.endMinutes),
|
endTime: toHm(block.endMinutes),
|
||||||
endMinutes: block.endMinutes,
|
endMinutes: block.endMinutes,
|
||||||
|
overnight: isOvernight(block),
|
||||||
|
durationMinutes: blockDuration(block),
|
||||||
proclamationIds
|
proclamationIds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ describe('GET /api/v2/schedule auth', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /api/v2/schedule success', () => {
|
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({
|
licenseKeyFindUnique.mockResolvedValue({
|
||||||
key: KEY,
|
key: KEY,
|
||||||
userId: 'user-1',
|
userId: 'user-1',
|
||||||
@@ -97,8 +97,8 @@ describe('GET /api/v2/schedule success', () => {
|
|||||||
blockFindMany.mockResolvedValue([
|
blockFindMany.mockResolvedValue([
|
||||||
{
|
{
|
||||||
id: 'block-1',
|
id: 'block-1',
|
||||||
startMinutes: 360,
|
startMinutes: 21 * 60,
|
||||||
endMinutes: 480,
|
endMinutes: 9 * 60,
|
||||||
proclamations: [{ proclamation: { id: 'p1' } }, { proclamation: { id: 'p2' } }]
|
proclamations: [{ proclamation: { id: 'p1' } }, { proclamation: { id: 'p2' } }]
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -111,10 +111,12 @@ describe('GET /api/v2/schedule success', () => {
|
|||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
id: 'block-1',
|
id: 'block-1',
|
||||||
startTime: '06:00',
|
startTime: '21:00',
|
||||||
startMinutes: 360,
|
startMinutes: 1260,
|
||||||
endTime: '08:00',
|
endTime: '09:00',
|
||||||
endMinutes: 480,
|
endMinutes: 540,
|
||||||
|
overnight: true,
|
||||||
|
durationMinutes: 720,
|
||||||
proclamationIds: ['p1', 'p2']
|
proclamationIds: ['p1', 'p2']
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -134,9 +136,20 @@ describe('GET /api/v2/schedule success', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const response = await call({ email: 'jane@example.com', licenseKey: KEY });
|
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 () => {
|
it('accepts the legacy `key` query alias and an email-only license', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user