From 9fd60bcfed9904c716acc21fccc4905dc502ffe7 Mon Sep 17 00:00:00 2001 From: Gronod Date: Tue, 19 May 2026 20:42:59 +0100 Subject: [PATCH] fix(webhooks): Use SONARR_INSTANCES/RADARR_INSTANCES config for notification routes The notification routes were using process.env.SONARR_URL directly, which is undefined when using the newer SONARR_INSTANCES JSON format. Changes: - Added getFirstSonarrInstance() and getFirstRadarrInstance() helpers - Updated /notifications, /notifications/test, and /notifications/sofarr-webhook routes to use instance config from getSonarrInstances()/getRadarrInstances() - Returns 503 error if no instances are configured Fixes: 'Invalid URL' errors when calling Sonarr/Radarr notification APIs --- server/routes/radarr.js | 43 ++++++++++++++++++++++++++++++----------- server/routes/sonarr.js | 43 ++++++++++++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/server/routes/radarr.js b/server/routes/radarr.js index c697f0f..eceb866 100644 --- a/server/routes/radarr.js +++ b/server/routes/radarr.js @@ -4,7 +4,16 @@ const axios = require('axios'); const router = express.Router(); const requireAuth = require('../middleware/requireAuth'); const sanitizeError = require('../utils/sanitizeError'); -const { getWebhookSecret, getSofarrBaseUrl } = require('../utils/config'); +const { getWebhookSecret, getSofarrBaseUrl, getRadarrInstances } = require('../utils/config'); + +// Helper to get first Radarr instance (for notification proxy routes) +function getFirstRadarrInstance() { + const instances = getRadarrInstances(); + if (!instances || instances.length === 0) { + return null; + } + return instances[0]; +} router.use(requireAuth); @@ -60,9 +69,13 @@ router.get('/movies', async (req, res) => { // Notification proxy routes (Phase 3) // GET /api/radarr/notifications - list all notifications router.get('/notifications', async (req, res) => { + const instance = getFirstRadarrInstance(); + if (!instance) { + return res.status(503).json({ error: 'Radarr not configured' }); + } try { - const response = await axios.get(`${process.env.RADARR_URL}/api/v3/notification`, { - headers: { 'X-Api-Key': process.env.RADARR_API_KEY } + const response = await axios.get(`${instance.url}/api/v3/notification`, { + headers: { 'X-Api-Key': instance.apiKey } }); res.json(response.data); } catch (error) { @@ -121,9 +134,13 @@ router.delete('/notifications/:id', async (req, res) => { // POST /api/radarr/notifications/test - test notification router.post('/notifications/test', async (req, res) => { + const instance = getFirstRadarrInstance(); + if (!instance) { + return res.status(503).json({ error: 'Radarr not configured' }); + } try { - const response = await axios.post(`${process.env.RADARR_URL}/api/v3/notification/test`, req.body, { - headers: { 'X-Api-Key': process.env.RADARR_API_KEY } + const response = await axios.post(`${instance.url}/api/v3/notification/test`, req.body, { + headers: { 'X-Api-Key': instance.apiKey } }); res.json(response.data); } catch (error) { @@ -150,6 +167,10 @@ router.get('/notifications/schema', async (req, res) => { // POST /api/radarr/notifications/sofarr-webhook - one-click Sofarr webhook setup router.post('/notifications/sofarr-webhook', async (req, res) => { + const instance = getFirstRadarrInstance(); + if (!instance) { + return res.status(503).json({ error: 'Radarr not configured' }); + } try { const sofarrBaseUrl = getSofarrBaseUrl(); const webhookSecret = getWebhookSecret(); @@ -164,8 +185,8 @@ router.post('/notifications/sofarr-webhook', async (req, res) => { const webhookUrl = `${sofarrBaseUrl}/api/webhook/radarr`; // Check if Sofarr webhook already exists - const listResponse = await axios.get(`${process.env.RADARR_URL}/api/v3/notification`, { - headers: { 'X-Api-Key': process.env.RADARR_API_KEY } + const listResponse = await axios.get(`${instance.url}/api/v3/notification`, { + headers: { 'X-Api-Key': instance.apiKey } }); const existingNotification = listResponse.data.find(n => n.name === 'Sofarr'); @@ -190,17 +211,17 @@ router.post('/notifications/sofarr-webhook', async (req, res) => { if (existingNotification) { // Update existing notification const response = await axios.put( - `${process.env.RADARR_URL}/api/v3/notification/${existingNotification.id}`, + `${instance.url}/api/v3/notification/${existingNotification.id}`, { ...notificationPayload, id: existingNotification.id }, - { headers: { 'X-Api-Key': process.env.RADARR_API_KEY } } + { headers: { 'X-Api-Key': instance.apiKey } } ); res.json(response.data); } else { // Create new notification const response = await axios.post( - `${process.env.RADARR_URL}/api/v3/notification`, + `${instance.url}/api/v3/notification`, notificationPayload, - { headers: { 'X-Api-Key': process.env.RADARR_API_KEY } } + { headers: { 'X-Api-Key': instance.apiKey } } ); res.json(response.data); } diff --git a/server/routes/sonarr.js b/server/routes/sonarr.js index 8d6b6d4..0704cb6 100644 --- a/server/routes/sonarr.js +++ b/server/routes/sonarr.js @@ -4,7 +4,16 @@ const axios = require('axios'); const router = express.Router(); const requireAuth = require('../middleware/requireAuth'); const sanitizeError = require('../utils/sanitizeError'); -const { getWebhookSecret, getSofarrBaseUrl } = require('../utils/config'); +const { getWebhookSecret, getSofarrBaseUrl, getSonarrInstances } = require('../utils/config'); + +// Helper to get first Sonarr instance (for notification proxy routes) +function getFirstSonarrInstance() { + const instances = getSonarrInstances(); + if (!instances || instances.length === 0) { + return null; + } + return instances[0]; +} router.use(requireAuth); @@ -60,9 +69,13 @@ router.get('/series', async (req, res) => { // Notification proxy routes (Phase 3) // GET /api/sonarr/notifications - list all notifications router.get('/notifications', async (req, res) => { + const instance = getFirstSonarrInstance(); + if (!instance) { + return res.status(503).json({ error: 'Sonarr not configured' }); + } try { - const response = await axios.get(`${process.env.SONARR_URL}/api/v3/notification`, { - headers: { 'X-Api-Key': process.env.SONARR_API_KEY } + const response = await axios.get(`${instance.url}/api/v3/notification`, { + headers: { 'X-Api-Key': instance.apiKey } }); res.json(response.data); } catch (error) { @@ -121,9 +134,13 @@ router.delete('/notifications/:id', async (req, res) => { // POST /api/sonarr/notifications/test - test notification router.post('/notifications/test', async (req, res) => { + const instance = getFirstSonarrInstance(); + if (!instance) { + return res.status(503).json({ error: 'Sonarr not configured' }); + } try { - const response = await axios.post(`${process.env.SONARR_URL}/api/v3/notification/test`, req.body, { - headers: { 'X-Api-Key': process.env.SONARR_API_KEY } + const response = await axios.post(`${instance.url}/api/v3/notification/test`, req.body, { + headers: { 'X-Api-Key': instance.apiKey } }); res.json(response.data); } catch (error) { @@ -150,6 +167,10 @@ router.get('/notifications/schema', async (req, res) => { // POST /api/sonarr/notifications/sofarr-webhook - one-click Sofarr webhook setup router.post('/notifications/sofarr-webhook', async (req, res) => { + const instance = getFirstSonarrInstance(); + if (!instance) { + return res.status(503).json({ error: 'Sonarr not configured' }); + } try { const sofarrBaseUrl = getSofarrBaseUrl(); const webhookSecret = getWebhookSecret(); @@ -164,8 +185,8 @@ router.post('/notifications/sofarr-webhook', async (req, res) => { const webhookUrl = `${sofarrBaseUrl}/api/webhook/sonarr`; // Check if Sofarr webhook already exists - const listResponse = await axios.get(`${process.env.SONARR_URL}/api/v3/notification`, { - headers: { 'X-Api-Key': process.env.SONARR_API_KEY } + const listResponse = await axios.get(`${instance.url}/api/v3/notification`, { + headers: { 'X-Api-Key': instance.apiKey } }); const existingNotification = listResponse.data.find(n => n.name === 'Sofarr'); @@ -190,17 +211,17 @@ router.post('/notifications/sofarr-webhook', async (req, res) => { if (existingNotification) { // Update existing notification const response = await axios.put( - `${process.env.SONARR_URL}/api/v3/notification/${existingNotification.id}`, + `${instance.url}/api/v3/notification/${existingNotification.id}`, { ...notificationPayload, id: existingNotification.id }, - { headers: { 'X-Api-Key': process.env.SONARR_API_KEY } } + { headers: { 'X-Api-Key': instance.apiKey } } ); res.json(response.data); } else { // Create new notification const response = await axios.post( - `${process.env.SONARR_URL}/api/v3/notification`, + `${instance.url}/api/v3/notification`, notificationPayload, - { headers: { 'X-Api-Key': process.env.SONARR_API_KEY } } + { headers: { 'X-Api-Key': instance.apiKey } } ); res.json(response.data); }