Skip to content
Open
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
4 changes: 1 addition & 3 deletions apps/backend/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { parse as parse_yaml } from 'yaml';
import swaggerContent from './app.yaml';
import { dj_route } from './routes/djs.route.js';
import { flowsheet_route } from './routes/flowsheet.route.js';
import { flowsheet_v2_route } from './routes/flowsheet.v2.route.js';

import { library_route } from './routes/library.route.js';
import { schedule_route } from './routes/schedule.route.js';
import { events_route } from './routes/events.route.js';
Expand Down Expand Up @@ -40,8 +40,6 @@ app.use('/library', library_route);

app.use('/flowsheet', flowsheet_route);

app.use('/v2/flowsheet', flowsheet_v2_route);

app.use('/djs', dj_route);

app.use('/request', request_line_route);
Expand Down
80 changes: 51 additions & 29 deletions apps/backend/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,47 @@ components:
request_flag:
type: boolean

FlowsheetEntryGet:
allOf:
- $ref: '#/components/schemas/FlowsheetEntry'
- type: object
properties:
id:
type: integer
play_order:
type: integer
show_id:
type: integer
rotation_play_freq:
type: string
message:
type: string
FlowsheetEntryV2:
type: object
required: [id, show_id, play_order, add_time, entry_type]
discriminator:
propertyName: entry_type
properties:
id:
type: integer
show_id:
type: integer
play_order:
type: integer
add_time:
type: string
format: date-time
entry_type:
type: string
enum: [track, show_start, show_end, dj_join, dj_leave, talkset, breakpoint, message]
description: >
Discriminated union keyed by entry_type.
track entries include artist_name, album_title, track_title, record_label, rotation_bin, metadata fields.
show_start/show_end entries include dj_name and timestamp.
dj_join/dj_leave entries include dj_name.
talkset/breakpoint/message entries include message.

FlowsheetPaginatedResponse:
type: object
required: [entries, total, page, limit, totalPages]
properties:
entries:
type: array
items:
$ref: '#/components/schemas/FlowsheetEntryV2'
total:
type: integer
page:
type: integer
limit:
type: integer
totalPages:
type: integer

FlowsheetEntryAdd:
type: object
Expand Down Expand Up @@ -183,7 +209,7 @@ components:
type: string
genre_name:
type: string
play_freq:
rotation_bin:
type: string
nullable: true
add_date:
Expand Down Expand Up @@ -219,7 +245,7 @@ components:
add_date:
type: string
format: date
play_freq:
rotation_bin:
type: string
enum: ['S', 'L', 'M', 'H']
kill_date:
Expand All @@ -231,11 +257,11 @@ components:

NewRotationRequest:
type: object
required: ['album_id', 'play_freq']
required: ['album_id', 'rotation_bin']
properties:
album_id:
type: integer
play_freq:
rotation_bin:
type: string
enum: ['S', 'L', 'M', 'H']
kill_date:
Expand Down Expand Up @@ -321,9 +347,7 @@ components:
preview:
type: array
items:
allOf:
- $ref: '#/components/schemas/FlowsheetEntry'
- $ref: '#/components/schemas/FlowsheetEntryGet'
$ref: '#/components/schemas/FlowsheetEntryV2'

security:
- BearerAuth: []
Expand Down Expand Up @@ -361,13 +385,11 @@ paths:
description: last play_order id in range ** Must be provided together with start_id
responses:
'200':
description: List of flowsheet entries
description: Paginated flowsheet entries (discriminated union format)
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/FlowsheetEntryGet'
$ref: '#/components/schemas/FlowsheetPaginatedResponse'

post:
summary: Add entry to flowsheet
Expand Down Expand Up @@ -480,11 +502,11 @@ paths:
summary: Get the most recent flowsheet entry
responses:
'200':
description: Latest flowsheet entry
description: Latest flowsheet entry (discriminated union format)
content:
application/json:
schema:
$ref: '#/components/schemas/FlowsheetEntryGet'
$ref: '#/components/schemas/FlowsheetEntryV2'

/flowsheet/join:
post:
Expand Down Expand Up @@ -654,7 +676,7 @@ paths:
entries:
type: array
items:
$ref: '#/components/schemas/FlowsheetEntry'
$ref: '#/components/schemas/FlowsheetEntryV2'

/djs:
get:
Expand Down
118 changes: 77 additions & 41 deletions apps/backend/controllers/flowsheet.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type QueryParams = {
shows_limit?: string;
};


export interface IFSEntryMetadata {
// Album metadata from cache
artwork_url: string | null;
Expand All @@ -28,7 +29,7 @@ export interface IFSEntryMetadata {
}

export interface IFSEntry extends FSEntry {
rotation_play_freq: string | null;
rotation_bin: string | null;
metadata: IFSEntryMetadata;
}

Expand All @@ -39,7 +40,6 @@ export const getEntries: RequestHandler<object, unknown, object, QueryParams> =

const page = parseInt(query.page ?? '0');
const limit = parseInt(query.limit ?? '30');
const offset = page * limit;

if (query.shows_limit !== undefined) {
try {
Expand All @@ -54,7 +54,7 @@ export const getEntries: RequestHandler<object, unknown, object, QueryParams> =
const entries = await flowsheet_service.getEntriesByShow(...recentShows.map((show) => show.id));

if (entries.length) {
res.status(200).json(entries);
res.status(200).json(entries.map(flowsheet_service.transformToV2));
} else {
res.status(404).json({
message: 'No Tracks found',
Expand All @@ -69,47 +69,72 @@ export const getEntries: RequestHandler<object, unknown, object, QueryParams> =
}
}

if (
parseInt(query.end_id ?? '0') - parseInt(query.start_id ?? '0') - DELETION_OFFSET > MAX_ITEMS ||
limit > MAX_ITEMS
) {
res.status(400).json({
message: 'Requested too many entries',
});
} else if (isNaN(limit) || limit < 1) {
res.status(400).json({
message: 'limit must be a positive number',
});
} else {
if (query.start_id !== undefined && query.end_id !== undefined) {
if (parseInt(query.end_id) - parseInt(query.start_id) - DELETION_OFFSET > MAX_ITEMS) {
res.status(400).json({ message: 'Requested too many entries' });
return;
}
try {
const entries: IFSEntry[] =
query.start_id !== undefined && query.end_id !== undefined
? await flowsheet_service.getEntriesByRange(parseInt(query.start_id), parseInt(query.end_id))
: await flowsheet_service.getEntriesByPage(offset, limit);
const entries = await flowsheet_service.getEntriesByRange(parseInt(query.start_id), parseInt(query.end_id));
if (entries.length) {
res.status(200).json(entries);
res.status(200).json(entries.map(flowsheet_service.transformToV2));
} else {
console.error('No Tracks found');
res.status(404).json({
message: 'No Tracks found',
});
res.status(404).json({ message: 'No Tracks found' });
}
} catch (e) {
console.error('Failed to retrieve tracks');
console.error(`Error: ${e}`);
next(e);
}
return;
}

// Default: paginated entries with discriminated union format
if (isNaN(limit) || limit < 1) {
res.status(400).json({ message: 'limit must be a positive number' });
return;
}

if (limit > MAX_ITEMS) {
res.status(400).json({ message: 'Requested too many entries' });
return;
}

if (isNaN(page) || page < 0) {
res.status(400).json({ message: 'page must be a non-negative number' });
return;
}

try {
const offset = page * limit;
const [entries, total] = await Promise.all([
flowsheet_service.getEntriesByPage(offset, limit),
flowsheet_service.getEntryCount(),
]);

const totalPages = Math.ceil(total / limit);

res.status(200).json({
entries: entries.map(flowsheet_service.transformToV2),
total,
page,
limit,
totalPages,
});
} catch (e) {
console.error('Failed to retrieve tracks');
console.error(`Error: ${e}`);
next(e);
}
};

export const getLatest: RequestHandler = async (req, res, next) => {
try {
const latest: IFSEntry[] = await flowsheet_service.getEntriesByPage(0, 1);
if (latest.length) {
res.status(200).json(latest[0]);
const entries = await flowsheet_service.getEntriesByPage(0, 1);
if (entries.length) {
res.status(200).json(flowsheet_service.transformToV2(entries[0]));
} else {
console.error('No Tracks found');
res.status(404).send('Error: No Tracks found');
res.status(404).json({ message: 'No entries found' });
}
} catch (e) {
console.error('Error: Failed to retrieve track');
Expand Down Expand Up @@ -406,25 +431,36 @@ export const changeOrder: RequestHandler<object, unknown, { entry_id: number; ne
}
};

export interface ShowInfo extends Show {
export interface ShowMetadata extends Show {
specialty_show_name: string;
show_djs: { id: string | null; dj_name: string | null }[];
}

export interface ShowInfo extends ShowMetadata {
entries: FSEntry[];
}

export const getShowInfo: RequestHandler<object, unknown, object, { show_id: string }> = async (req, res, next) => {
const showId = parseInt(req.query.show_id);

if (isNaN(showId)) {
console.error('Bad Request, Missing Show Identifier: show_id');
res.status(400).send('Bad Request, Missing Show Identifier: show_id');
} else {
try {
const showInfo = await flowsheet_service.getPlaylist(showId);
res.status(200).json(showInfo);
} catch (e) {
console.error('Error: Failed to retrieve playlist');
console.error(e);
next(e);
}
res.status(400).json({ message: 'Missing or invalid show_id parameter' });
return;
}

try {
const [showMetadata, entries] = await Promise.all([
flowsheet_service.getShowMetadata(showId),
flowsheet_service.getEntriesByShow(showId),
]);

res.status(200).json({
...showMetadata,
entries: entries.map(flowsheet_service.transformToV2),
});
} catch (e) {
console.error('Error: Failed to retrieve playlist');
console.error(e);
next(e);
}
};
33 changes: 0 additions & 33 deletions apps/backend/controllers/flowsheet.v2.controller.ts

This file was deleted.

4 changes: 2 additions & 2 deletions apps/backend/controllers/library.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ export const getRotation: RequestHandler = async (req, res, next) => {

export type RotationAddRequest = Omit<NewRotationRelease, 'id'>;
export const addRotation: RequestHandler<object, unknown, NewRotationRelease> = async (req, res, next) => {
if (req.body.album_id === undefined || req.body.play_freq === undefined) {
res.status(400).send('Missing Parameters: album_id or play_freq');
if (req.body.album_id === undefined || req.body.rotation_bin === undefined) {
res.status(400).send('Missing Parameters: album_id or rotation_bin');
} else {
try {
const rotationRelease: RotationRelease = await libraryService.addToRotation(req.body);
Expand Down
7 changes: 0 additions & 7 deletions apps/backend/routes/flowsheet.v2.route.ts

This file was deleted.

Loading