From 0437d831251584a2af83d5d5128a01037cb3c77d Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Sat, 21 Feb 2026 08:27:24 -0800 Subject: [PATCH 1/3] fix: standardize error response format to { message } across all endpoints Mixed formats (plain strings, { status, message }, leaked error.message) are now consistently { message: '...' }. Internal errors no longer expose raw error messages to clients. Co-authored-by: Cursor --- apps/backend/controllers/djs.controller.ts | 10 ++-- .../controllers/flowsheet.controller.ts | 12 ++-- .../backend/controllers/library.controller.ts | 37 ++++++------ apps/backend/middleware/errorHandler.ts | 3 +- tests/unit/middleware/errorHandler.test.ts | 56 +++++++++++++++++++ 5 files changed, 86 insertions(+), 32 deletions(-) create mode 100644 tests/unit/middleware/errorHandler.test.ts diff --git a/apps/backend/controllers/djs.controller.ts b/apps/backend/controllers/djs.controller.ts index 1e160cfd..72ad9155 100644 --- a/apps/backend/controllers/djs.controller.ts +++ b/apps/backend/controllers/djs.controller.ts @@ -12,7 +12,7 @@ export type binBody = { export const addToBin: RequestHandler = async (req, res, next) => { if (req.body.album_id === undefined || req.body.dj_id === undefined) { console.error('Bad Request, Missing Album Identifier: album_id'); - res.status(400).send('Bad Request, Missing DJ or album identifier: album_id'); + res.status(400).json({ message: 'Bad Request, Missing DJ or album identifier: album_id' }); } else { const bin_entry: NewBinEntry = { dj_id: req.body.dj_id, @@ -40,7 +40,7 @@ export type binQuery = { export const deleteFromBin: RequestHandler = async (req, res, next) => { if (req.query.album_id === undefined || req.query.dj_id === undefined) { console.error('Bad Request, Missing Bin Entry Identifier: album_id or dj_id'); - res.status(400).send('Bad Request, Missing Bin Entry Identifier: album_id or dj_id'); + res.status(400).json({ message: 'Bad Request, Missing Bin Entry Identifier: album_id or dj_id' }); } else { try { //check that the dj_id === dj_id of bin entry @@ -56,7 +56,7 @@ export const deleteFromBin: RequestHandler = export const getBin: RequestHandler = async (req, res, next) => { if (req.query.dj_id === undefined) { console.error('Bad Request, Missing DJ Identifier: dj_id'); - res.status(400).send('Bad Request, Missing DJ Identifier: dj_id'); + res.status(400).json({ message: 'Bad Request, Missing DJ Identifier: dj_id' }); } else { try { const dj_bin = await DJService.getBinFromDB(req.query.dj_id); @@ -72,7 +72,7 @@ export const getBin: RequestHandler export const getPlaylistsForDJ: RequestHandler = async (req, res, next) => { if (req.query.dj_id === undefined) { console.error('Bad Request, Missing DJ Identifier: dj_id'); - res.status(400).send('Bad Request, Missing DJ Identifier: dj_id'); + res.status(400).json({ message: 'Bad Request, Missing DJ Identifier: dj_id' }); } else { try { const playlists = await DJService.getPlaylistsForDJ(req.query.dj_id); @@ -88,7 +88,7 @@ export const getPlaylistsForDJ: RequestHandler = async (req, res, next) => { if (req.query.playlist_id === undefined) { console.error('Bad Request, Missing Playlist Identifier: playlist_id'); - res.status(400).send('Bad Request, Missing Playlist Identifier: playlist_id'); + res.status(400).json({ message: 'Bad Request, Missing Playlist Identifier: playlist_id' }); } else { try { const playlist = await DJService.getPlaylist(parseInt(req.query.playlist_id)); diff --git a/apps/backend/controllers/flowsheet.controller.ts b/apps/backend/controllers/flowsheet.controller.ts index 9ac09f27..832f3c1a 100644 --- a/apps/backend/controllers/flowsheet.controller.ts +++ b/apps/backend/controllers/flowsheet.controller.ts @@ -166,13 +166,13 @@ export const addEntry: RequestHandler = async (req: Request const { entry_id } = req.body; if (entry_id === undefined) { console.error('Bad Request, Missing entry identifier: entry_id'); - res.status(400).send('Bad Request, Missing entry identifier: entry_id'); + res.status(400).json({ message: 'Bad Request, Missing entry identifier: entry_id' }); } else { try { const removedEntry: FSEntry = await flowsheet_service.removeTrack(entry_id); @@ -297,7 +297,7 @@ export const updateEntry: RequestHandler, res, next) => { const current_show = await flowsheet_service.getLatestShow(); if (req.body.dj_id === undefined) { - res.status(400).send('Bad Request, Must include a dj_id to join show'); + res.status(400).json({ message: 'Bad Request, Must include a dj_id to join show' }); } else if (current_show?.end_time !== null) { try { const show_session: Show = await flowsheet_service.startShow( diff --git a/apps/backend/controllers/library.controller.ts b/apps/backend/controllers/library.controller.ts index 5af246c8..b7adb442 100644 --- a/apps/backend/controllers/library.controller.ts +++ b/apps/backend/controllers/library.controller.ts @@ -34,7 +34,6 @@ export const addAlbum: RequestHandler = async (req: Request { export type RotationAddRequest = Omit; export const addRotation: RequestHandler = async (req, res, next) => { if (req.body.album_id === undefined || req.body.rotation_bin === undefined) { - res.status(400).send('Missing Parameters: album_id or rotation_bin'); + res.status(400).json({ message: 'Missing Parameters: album_id or rotation_bin' }); } else { try { const rotationRelease: RotationRelease = await libraryService.addToRotation(req.body); @@ -238,16 +235,16 @@ export const killRotation: RequestHandler const { body } = req; if (body.rotation_id === undefined) { - res.status(400).send('Bad Request, Missing Parameter: rotation_id'); + res.status(400).json({ message: 'Bad Request, Missing Parameter: rotation_id' }); } else if (body.kill_date !== undefined && !libraryService.isISODate(body.kill_date)) { - res.status(400).send('Bad Request, Incorrect Date Format: kill_date should be of form YYYY-MM-DD'); + res.status(400).json({ message: 'Bad Request, Incorrect Date Format: kill_date should be of form YYYY-MM-DD' }); } else { try { const updatedRotation: RotationRelease = await libraryService.killRotationInDB(body.rotation_id, body.kill_date); if (updatedRotation !== undefined) { res.status(200).json(updatedRotation); } else { - res.status(400).json({ status: 400, message: 'Rotation entry not found' }); + res.status(400).json({ message: 'Rotation entry not found' }); } } catch (e) { console.error('Failed to update rotation kill_date'); @@ -271,7 +268,7 @@ export const getFormats: RequestHandler = async (req, res, next) => { export const addFormat: RequestHandler = async (req, res, next) => { const { body } = req; if (body.name === undefined) { - res.status(400).send('Bad Request, Missing Parameter: name'); + res.status(400).json({ message: 'Bad Request, Missing Parameter: name' }); } else { try { const newFormat: NewAlbumFormat = { @@ -296,7 +293,7 @@ export const getGenres: RequestHandler = async (req, res) => { export const addGenre: RequestHandler = async (req, res, next) => { const { body } = req; if (body.name === undefined || body.description === undefined) { - res.status(400).send('Bad Request, Parameters name and description are required.'); + res.status(400).json({ message: 'Bad Request, Parameters name and description are required.' }); } else { try { const newGenre: NewGenre = { @@ -322,7 +319,7 @@ export const getAlbum: RequestHandler { + it('returns { message } with correct status for WxycError', () => { + const res = mockResponse(); + const error = new WxycError('Album not found', 404); + + errorHandler(error, mockReq, res, mockNext); + + expect(res.status).toHaveBeenCalledWith(404); + expect(res.json).toHaveBeenCalledWith({ message: 'Album not found' }); + }); + + it('returns generic message for non-WxycError (does not leak internals)', () => { + const res = mockResponse(); + const error = new Error('SELECT * FROM users failed: connection refused'); + + errorHandler(error, mockReq, res, mockNext); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ message: 'Internal server error' }); + }); + + it('handles non-Error values thrown', () => { + const res = mockResponse(); + + errorHandler('something broke', mockReq, res, mockNext); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ message: 'Internal server error' }); + }); + + it('logs non-WxycError errors to console', () => { + const res = mockResponse(); + const error = new Error('db connection lost'); + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + + errorHandler(error, mockReq, res, mockNext); + + expect(consoleSpy).toHaveBeenCalledWith('Unhandled error:', error); + consoleSpy.mockRestore(); + }); +}); From bb4fa96fb6bea78ce6ce9ce86a950a56b2198703 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Fri, 27 Feb 2026 14:01:15 -0800 Subject: [PATCH 2/3] fix: resolve lint errors --- .../controllers/flowsheet.controller.ts | 4 ++- tests/unit/middleware/errorHandler.test.ts | 32 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/backend/controllers/flowsheet.controller.ts b/apps/backend/controllers/flowsheet.controller.ts index 832f3c1a..7bd4a47b 100644 --- a/apps/backend/controllers/flowsheet.controller.ts +++ b/apps/backend/controllers/flowsheet.controller.ts @@ -213,7 +213,9 @@ export const addEntry: RequestHandler = async (req: Request { it('returns { message } with correct status for WxycError', () => { - const res = mockResponse(); + const { res, statusMock, jsonMock } = mockResponse(); const error = new WxycError('Album not found', 404); errorHandler(error, mockReq, res, mockNext); - expect(res.status).toHaveBeenCalledWith(404); - expect(res.json).toHaveBeenCalledWith({ message: 'Album not found' }); + expect(statusMock).toHaveBeenCalledWith(404); + expect(jsonMock).toHaveBeenCalledWith({ message: 'Album not found' }); }); it('returns generic message for non-WxycError (does not leak internals)', () => { - const res = mockResponse(); + const { res, statusMock, jsonMock } = mockResponse(); const error = new Error('SELECT * FROM users failed: connection refused'); errorHandler(error, mockReq, res, mockNext); - expect(res.status).toHaveBeenCalledWith(500); - expect(res.json).toHaveBeenCalledWith({ message: 'Internal server error' }); + expect(statusMock).toHaveBeenCalledWith(500); + expect(jsonMock).toHaveBeenCalledWith({ message: 'Internal server error' }); }); it('handles non-Error values thrown', () => { - const res = mockResponse(); + const { res, statusMock, jsonMock } = mockResponse(); errorHandler('something broke', mockReq, res, mockNext); - expect(res.status).toHaveBeenCalledWith(500); - expect(res.json).toHaveBeenCalledWith({ message: 'Internal server error' }); + expect(statusMock).toHaveBeenCalledWith(500); + expect(jsonMock).toHaveBeenCalledWith({ message: 'Internal server error' }); }); it('logs non-WxycError errors to console', () => { - const res = mockResponse(); + const { res } = mockResponse(); const error = new Error('db connection lost'); const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); From 15c96a0cb2525ebd064f4f425dd9a40f9f2c1762 Mon Sep 17 00:00:00 2001 From: Jake Bromberg Date: Mon, 9 Mar 2026 23:24:54 -0700 Subject: [PATCH 3/3] refactor: throw WxycError instead of inline error responses Replaces res.status().json/send() error patterns with throw new WxycError() so the centralized error handler formats all error responses consistently. --- apps/backend/controllers/djs.controller.ts | 116 ++++--- .../controllers/flowsheet.controller.ts | 221 ++++++------- .../backend/controllers/library.controller.ts | 304 +++++++++--------- 3 files changed, 313 insertions(+), 328 deletions(-) diff --git a/apps/backend/controllers/djs.controller.ts b/apps/backend/controllers/djs.controller.ts index 72ad9155..ee846b20 100644 --- a/apps/backend/controllers/djs.controller.ts +++ b/apps/backend/controllers/djs.controller.ts @@ -1,6 +1,7 @@ import { RequestHandler } from 'express'; import * as DJService from '../services/djs.service'; import { NewBinEntry } from '@wxyc/database'; +import WxycError from '../utils/error.js'; export type binBody = { dj_id: string; @@ -11,22 +12,21 @@ export type binBody = { export const addToBin: RequestHandler = async (req, res, next) => { if (req.body.album_id === undefined || req.body.dj_id === undefined) { - console.error('Bad Request, Missing Album Identifier: album_id'); - res.status(400).json({ message: 'Bad Request, Missing DJ or album identifier: album_id' }); - } else { - const bin_entry: NewBinEntry = { - dj_id: req.body.dj_id, - album_id: req.body.album_id, - track_title: req.body.track_title === undefined ? null : req.body.track_title, - }; - try { - const added_bin_item = await DJService.addToBin(bin_entry); - res.status(200).json(added_bin_item); - } catch (e) { - console.error('Server error: Failed to insert into bin'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing DJ or album identifier: album_id', 400); + } + + const bin_entry: NewBinEntry = { + dj_id: req.body.dj_id, + album_id: req.body.album_id, + track_title: req.body.track_title === undefined ? null : req.body.track_title, + }; + try { + const added_bin_item = await DJService.addToBin(bin_entry); + res.status(200).json(added_bin_item); + } catch (e) { + console.error('Server error: Failed to insert into bin'); + console.error(e); + next(e); } }; @@ -39,64 +39,60 @@ export type binQuery = { export const deleteFromBin: RequestHandler = async (req, res, next) => { if (req.query.album_id === undefined || req.query.dj_id === undefined) { - console.error('Bad Request, Missing Bin Entry Identifier: album_id or dj_id'); - res.status(400).json({ message: 'Bad Request, Missing Bin Entry Identifier: album_id or dj_id' }); - } else { - try { - //check that the dj_id === dj_id of bin entry - const removed_bin_item = await DJService.removeFromBin(parseInt(req.query.album_id), req.query.dj_id); - res.status(200).json(removed_bin_item); - } catch (e) { - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing Bin Entry Identifier: album_id or dj_id', 400); + } + + try { + //check that the dj_id === dj_id of bin entry + const removed_bin_item = await DJService.removeFromBin(parseInt(req.query.album_id), req.query.dj_id); + res.status(200).json(removed_bin_item); + } catch (e) { + console.error(e); + next(e); } }; export const getBin: RequestHandler = async (req, res, next) => { if (req.query.dj_id === undefined) { - console.error('Bad Request, Missing DJ Identifier: dj_id'); - res.status(400).json({ message: 'Bad Request, Missing DJ Identifier: dj_id' }); - } else { - try { - const dj_bin = await DJService.getBinFromDB(req.query.dj_id); - res.status(200).json(dj_bin); - } catch (e) { - console.error("Error: Failed to retrieve dj's bin"); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing DJ Identifier: dj_id', 400); + } + + try { + const dj_bin = await DJService.getBinFromDB(req.query.dj_id); + res.status(200).json(dj_bin); + } catch (e) { + console.error("Error: Failed to retrieve dj's bin"); + console.error(e); + next(e); } }; export const getPlaylistsForDJ: RequestHandler = async (req, res, next) => { if (req.query.dj_id === undefined) { - console.error('Bad Request, Missing DJ Identifier: dj_id'); - res.status(400).json({ message: 'Bad Request, Missing DJ Identifier: dj_id' }); - } else { - try { - const playlists = await DJService.getPlaylistsForDJ(req.query.dj_id); - res.status(200).json(playlists); - } catch (e) { - console.error('Error: Failed to retrieve playlists'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing DJ Identifier: dj_id', 400); + } + + try { + const playlists = await DJService.getPlaylistsForDJ(req.query.dj_id); + res.status(200).json(playlists); + } catch (e) { + console.error('Error: Failed to retrieve playlists'); + console.error(e); + next(e); } }; export const getPlaylist: RequestHandler = async (req, res, next) => { if (req.query.playlist_id === undefined) { - console.error('Bad Request, Missing Playlist Identifier: playlist_id'); - res.status(400).json({ message: 'Bad Request, Missing Playlist Identifier: playlist_id' }); - } else { - try { - const playlist = await DJService.getPlaylist(parseInt(req.query.playlist_id)); - res.status(200).json(playlist); - } catch (e) { - console.error('Error: Failed to retrieve playlist'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing Playlist Identifier: playlist_id', 400); + } + + try { + const playlist = await DJService.getPlaylist(parseInt(req.query.playlist_id)); + res.status(200).json(playlist); + } catch (e) { + console.error('Error: Failed to retrieve playlist'); + console.error(e); + next(e); } }; diff --git a/apps/backend/controllers/flowsheet.controller.ts b/apps/backend/controllers/flowsheet.controller.ts index 7bd4a47b..92b3f3b8 100644 --- a/apps/backend/controllers/flowsheet.controller.ts +++ b/apps/backend/controllers/flowsheet.controller.ts @@ -3,6 +3,7 @@ import { Mutex } from 'async-mutex'; import { NewFSEntry, FSEntry, Show, ShowDJ, library } from '@wxyc/database'; import * as flowsheet_service from '../services/flowsheet.service.js'; import { fetchAndCacheMetadata } from '../services/metadata/index.js'; +import WxycError from '../utils/error.js'; export type QueryParams = { page?: string; @@ -165,120 +166,111 @@ export const addEntry: RequestHandler = async (req: Request console.error('[Flowsheet] Metadata fetch failed:', err)); - } - - res.status(200).json(completedEntry); - } else if ( - body.album_title === undefined || - body.artist_name === undefined || - body.track_title === undefined - ) { - console.error('Bad Request, Missing Flowsheet Parameters: album_title, artist_name, track_title'); - res - .status(400) - .json({ message: 'Bad Request, Missing Flowsheet Parameters: album_title, artist_name, track_title' }); - } else { - const fsEntry: NewFSEntry = { - ...body, - show_id: latestShow.id, - }; - - const completedEntry: FSEntry = await flowsheet_service.addTrack(fsEntry); - - // Fire-and-forget: fetch metadata for this entry - if (completedEntry.artist_name) { - fetchAndCacheMetadata({ - albumId: completedEntry.album_id ?? undefined, - artistId: undefined, - rotationId: completedEntry.rotation_id ?? undefined, - artistName: completedEntry.artist_name, - albumTitle: completedEntry.album_title ?? undefined, - trackTitle: completedEntry.track_title ?? undefined, - }).catch((err) => console.error('[Flowsheet] Metadata fetch failed:', err)); - } - - res.status(200).json(completedEntry); - } - } catch (e) { - console.error('Error: Failed to add track to flowsheet'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, There are no active shows', 400); + } + + if (body.message !== undefined) { + //we're just throwing the message in there (whatever it may be): dj join event, psa event, talk set event, break-point + const fsEntry: NewFSEntry = { + artist_name: '', + album_title: '', + track_title: '', + message: body.message, + show_id: latestShow.id, + }; + try { + const completedEntry: FSEntry = await flowsheet_service.addTrack(fsEntry); + res.status(200).json(completedEntry); + } catch (e) { + console.error('Error: Failed to add message to flowsheet'); + console.error(e); + next(e); + } + return; + } + + // no message passed, so we assume we're adding a track to the flowsheet + if (body.track_title === undefined) { + throw new WxycError('Bad Request, Missing query parameter: track_title', 400); + } + + try { + if (body.album_id !== undefined) { + //backfill album info from library before adding to flowsheet + const albumInfo = await flowsheet_service.getAlbumFromDB(body.album_id); + + if (body.record_label !== undefined) { + albumInfo.record_label = body.record_label; + } + + const fsEntry: NewFSEntry = { + album_id: body.album_id, + ...albumInfo, + track_title: body.track_title, + rotation_id: body.rotation_id, + request_flag: body.request_flag, + show_id: latestShow.id, + }; + + const completedEntry: FSEntry = await flowsheet_service.addTrack(fsEntry); + + // Fire-and-forget: fetch metadata for this entry + if (completedEntry.artist_name) { + fetchAndCacheMetadata({ + albumId: completedEntry.album_id ?? undefined, + artistId: albumInfo.artist_id ?? undefined, + rotationId: completedEntry.rotation_id ?? undefined, + artistName: completedEntry.artist_name, + albumTitle: completedEntry.album_title ?? undefined, + trackTitle: completedEntry.track_title ?? undefined, + }).catch((err) => console.error('[Flowsheet] Metadata fetch failed:', err)); } + + res.status(200).json(completedEntry); + } else if (body.album_title === undefined || body.artist_name === undefined || body.track_title === undefined) { + throw new WxycError('Bad Request, Missing Flowsheet Parameters: album_title, artist_name, track_title', 400); } else { - //we're just throwing the message in there (whatever it may be): dj join event, psa event, talk set event, break-point const fsEntry: NewFSEntry = { - artist_name: '', - album_title: '', - track_title: '', - message: body.message, + ...body, show_id: latestShow.id, }; - try { - const completedEntry: FSEntry = await flowsheet_service.addTrack(fsEntry); - res.status(200).json(completedEntry); - } catch (e) { - console.error('Error: Failed to add message to flowsheet'); - console.error(e); - next(e); + + const completedEntry: FSEntry = await flowsheet_service.addTrack(fsEntry); + + // Fire-and-forget: fetch metadata for this entry + if (completedEntry.artist_name) { + fetchAndCacheMetadata({ + albumId: completedEntry.album_id ?? undefined, + artistId: undefined, + rotationId: completedEntry.rotation_id ?? undefined, + artistName: completedEntry.artist_name, + albumTitle: completedEntry.album_title ?? undefined, + trackTitle: completedEntry.track_title ?? undefined, + }).catch((err) => console.error('[Flowsheet] Metadata fetch failed:', err)); } + + res.status(200).json(completedEntry); } + } catch (e) { + console.error('Error: Failed to add track to flowsheet'); + console.error(e); + next(e); } }; export const deleteEntry: RequestHandler = async (req, res, next) => { const { entry_id } = req.body; if (entry_id === undefined) { - console.error('Bad Request, Missing entry identifier: entry_id'); - res.status(400).json({ message: 'Bad Request, Missing entry identifier: entry_id' }); - } else { - try { - const removedEntry: FSEntry = await flowsheet_service.removeTrack(entry_id); - res.status(200).json(removedEntry); - } catch (e) { - console.error('Error: Failed to remove entry'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing entry identifier: entry_id', 400); + } + + try { + const removedEntry: FSEntry = await flowsheet_service.removeTrack(entry_id); + res.status(200).json(removedEntry); + } catch (e) { + console.error('Error: Failed to remove entry'); + console.error(e); + next(e); } }; @@ -298,17 +290,16 @@ export const updateEntry: RequestHandler { const { entry_id, data } = req.body; if (entry_id === undefined) { - console.error('Bad Request, Missing entry identifier: entry_id'); - res.status(400).json({ message: 'Bad Request, Missing entry identifier: entry_id' }); - } else { - try { - const updatedEntry: FSEntry = await flowsheet_service.updateEntry(entry_id, data); - res.status(200).json(updatedEntry); - } catch (e) { - console.error('Error: Failed to update entry'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, Missing entry identifier: entry_id', 400); + } + + try { + const updatedEntry: FSEntry = await flowsheet_service.updateEntry(entry_id, data); + res.status(200).json(updatedEntry); + } catch (e) { + console.error('Error: Failed to update entry'); + console.error(e); + next(e); } }; @@ -322,8 +313,10 @@ export type JoinRequestBody = { export const joinShow: RequestHandler = async (req: Request, res, next) => { const current_show = await flowsheet_service.getLatestShow(); if (req.body.dj_id === undefined) { - res.status(400).json({ message: 'Bad Request, Must include a dj_id to join show' }); - } else if (current_show?.end_time !== null) { + throw new WxycError('Bad Request, Must include a dj_id to join show', 400); + } + + if (current_show?.end_time !== null) { try { const show_session: Show = await flowsheet_service.startShow( req.body.dj_id, diff --git a/apps/backend/controllers/library.controller.ts b/apps/backend/controllers/library.controller.ts index b7adb442..5faa4a92 100644 --- a/apps/backend/controllers/library.controller.ts +++ b/apps/backend/controllers/library.controller.ts @@ -10,6 +10,7 @@ import { RotationRelease, } from '@wxyc/database'; import * as libraryService from '../services/library.service.js'; +import WxycError from '../utils/error.js'; type NewAlbumRequest = { album_title: string; @@ -33,47 +34,45 @@ export const addAlbum: RequestHandler = async (req: Request { const { query } = req; if (!query.code_letters || !query.genre_id) { - res.status(400); - res.send('Missing query parameters: code_letters and genre_id'); - return; + throw new WxycError('Missing query parameters: code_letters and genre_id', 400); } const genreId = Number(query.genre_id); if (!Number.isFinite(genreId)) { - res.status(400); - res.send('Invalid genre_id'); - return; + throw new WxycError('Invalid genre_id', 400); } try { @@ -214,15 +210,15 @@ export const getRotation: RequestHandler = async (req, res, next) => { export type RotationAddRequest = Omit; export const addRotation: RequestHandler = async (req, res, next) => { if (req.body.album_id === undefined || req.body.rotation_bin === undefined) { - res.status(400).json({ message: 'Missing Parameters: album_id or rotation_bin' }); - } else { - try { - const rotationRelease: RotationRelease = await libraryService.addToRotation(req.body); - res.status(200).json(rotationRelease); - } catch (e) { - console.error(e); - next(e); - } + throw new WxycError('Missing Parameters: album_id or rotation_bin', 400); + } + + try { + const rotationRelease: RotationRelease = await libraryService.addToRotation(req.body); + res.status(200).json(rotationRelease); + } catch (e) { + console.error(e); + next(e); } }; @@ -235,22 +231,23 @@ export const killRotation: RequestHandler const { body } = req; if (body.rotation_id === undefined) { - res.status(400).json({ message: 'Bad Request, Missing Parameter: rotation_id' }); - } else if (body.kill_date !== undefined && !libraryService.isISODate(body.kill_date)) { - res.status(400).json({ message: 'Bad Request, Incorrect Date Format: kill_date should be of form YYYY-MM-DD' }); - } else { - try { - const updatedRotation: RotationRelease = await libraryService.killRotationInDB(body.rotation_id, body.kill_date); - if (updatedRotation !== undefined) { - res.status(200).json(updatedRotation); - } else { - res.status(400).json({ message: 'Rotation entry not found' }); - } - } catch (e) { - console.error('Failed to update rotation kill_date'); - console.error(e); - next(e); + throw new WxycError('Bad Request, Missing Parameter: rotation_id', 400); + } + if (body.kill_date !== undefined && !libraryService.isISODate(body.kill_date)) { + throw new WxycError('Bad Request, Incorrect Date Format: kill_date should be of form YYYY-MM-DD', 400); + } + + try { + const updatedRotation: RotationRelease = await libraryService.killRotationInDB(body.rotation_id, body.kill_date); + if (updatedRotation !== undefined) { + res.status(200).json(updatedRotation); + } else { + throw new WxycError('Rotation entry not found', 400); } + } catch (e) { + console.error('Failed to update rotation kill_date'); + console.error(e); + next(e); } }; @@ -268,20 +265,20 @@ export const getFormats: RequestHandler = async (req, res, next) => { export const addFormat: RequestHandler = async (req, res, next) => { const { body } = req; if (body.name === undefined) { - res.status(400).json({ message: 'Bad Request, Missing Parameter: name' }); - } else { - try { - const newFormat: NewAlbumFormat = { - format_name: body.name, - }; + throw new WxycError('Bad Request, Missing Parameter: name', 400); + } - const insertion = await libraryService.insertFormat(newFormat); - res.status(200).json(insertion); - } catch (e) { - console.error('Failed to add new format'); - console.error(e); - next(e); - } + try { + const newFormat: NewAlbumFormat = { + format_name: body.name, + }; + + const insertion = await libraryService.insertFormat(newFormat); + res.status(200).json(insertion); + } catch (e) { + console.error('Failed to add new format'); + console.error(e); + next(e); } }; @@ -293,41 +290,40 @@ export const getGenres: RequestHandler = async (req, res) => { export const addGenre: RequestHandler = async (req, res, next) => { const { body } = req; if (body.name === undefined || body.description === undefined) { - res.status(400).json({ message: 'Bad Request, Parameters name and description are required.' }); - } else { - try { - const newGenre: NewGenre = { - genre_name: body.name, - description: body.description, - plays: 0, - add_date: new Date().toISOString(), - last_modified: new Date(), - }; + throw new WxycError('Bad Request, Parameters name and description are required.', 400); + } - const insertion = await libraryService.insertGenre(newGenre); + try { + const newGenre: NewGenre = { + genre_name: body.name, + description: body.description, + plays: 0, + add_date: new Date().toISOString(), + last_modified: new Date(), + }; - res.status(200).json(insertion); - } catch (e) { - console.error('Failed to add new genre'); - console.error(e); - next(e); - } + const insertion = await libraryService.insertGenre(newGenre); + + res.status(200).json(insertion); + } catch (e) { + console.error('Failed to add new genre'); + console.error(e); + next(e); } }; export const getAlbum: RequestHandler = async (req, res, next) => { const { query } = req; if (query.album_id === undefined) { - console.error('Bad Request, missing album identifier: album_id'); - res.status(400).json({ message: 'Bad Request, missing album identifier: album_id' }); - } else { - try { - const album = await libraryService.getAlbumFromDB(parseInt(query.album_id)); - res.status(200).json(album); - } catch (e) { - console.error('Failed to retrieve album'); - console.error(e); - next(e); - } + throw new WxycError('Bad Request, missing album identifier: album_id', 400); + } + + try { + const album = await libraryService.getAlbumFromDB(parseInt(query.album_id)); + res.status(200).json(album); + } catch (e) { + console.error('Failed to retrieve album'); + console.error(e); + next(e); } };