feat: implement Pluggable Abstraction Layer for Data Retrieval (PALDRA) - #19
All checks were successful
Build and Push Docker Image / build (push) Successful in 42s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 46s
CI / Security audit (push) Successful in 1m21s
CI / Tests & coverage (push) Successful in 1m35s

- Create ArrRetriever abstract base class defining pluggable interface
- Implement PollingSonarrRetriever and PollingRadarrRetriever with HTTP polling
- Add ArrRetrieverRegistry for managing retriever instances
- Refactor poller.js to use retriever registry instead of direct Axios calls
- Update historyFetcher.js to use retriever registry
- Preserve all cache keys, TTLs, timing logs, SSE broadcasts, error handling
- Enable future webhook listeners without touching poller logic
This commit is contained in:
2026-05-19 14:43:28 +01:00
parent 9343486705
commit 627329df2f
6 changed files with 664 additions and 77 deletions

View File

@@ -2,6 +2,7 @@
const axios = require('axios');
const cache = require('./cache');
const { initializeClients, getAllDownloads, getDownloadsByClientType } = require('./downloadClients');
const { initializeRetrievers, getTagsByType, getQueuesByType, getHistoryByType } = require('./arrRetrievers');
const {
getSonarrInstances,
getRadarrInstances
@@ -38,8 +39,9 @@ async function pollAllServices() {
const start = Date.now();
try {
// Ensure download clients are initialized
// Ensure download clients and *arr retrievers are initialized
await initializeClients();
await initializeRetrievers();
const sonarrInstances = getSonarrInstances();
const radarrInstances = getRadarrInstances();
@@ -50,58 +52,30 @@ async function pollAllServices() {
const downloadsByType = await getDownloadsByClientType();
return downloadsByType;
}),
timed('Sonarr Tags', () => Promise.all(sonarrInstances.map(inst =>
axios.get(`${inst.url}/api/v3/tag`, {
headers: { 'X-Api-Key': inst.apiKey }
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
console.error(`[Poller] Sonarr ${inst.id} tags error:`, err.message);
return { instance: inst.id, data: [] };
})
))),
timed('Sonarr Queue', () => Promise.all(sonarrInstances.map(inst =>
axios.get(`${inst.url}/api/v3/queue`, {
headers: { 'X-Api-Key': inst.apiKey },
params: { includeSeries: true, includeEpisode: true }
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
console.error(`[Poller] Sonarr ${inst.id} queue error:`, err.message);
return { instance: inst.id, data: { records: [] } };
})
))),
timed('Sonarr History', () => Promise.all(sonarrInstances.map(inst =>
axios.get(`${inst.url}/api/v3/history`, {
headers: { 'X-Api-Key': inst.apiKey },
params: { pageSize: 10, includeEpisode: true }
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
console.error(`[Poller] Sonarr ${inst.id} history error:`, err.message);
return { instance: inst.id, data: { records: [] } };
})
))),
timed('Radarr Queue', () => Promise.all(radarrInstances.map(inst =>
axios.get(`${inst.url}/api/v3/queue`, {
headers: { 'X-Api-Key': inst.apiKey },
params: { includeMovie: true }
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
console.error(`[Poller] Radarr ${inst.id} queue error:`, err.message);
return { instance: inst.id, data: { records: [] } };
})
))),
timed('Radarr History', () => Promise.all(radarrInstances.map(inst =>
axios.get(`${inst.url}/api/v3/history`, {
headers: { 'X-Api-Key': inst.apiKey },
params: { pageSize: 10 }
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
console.error(`[Poller] Radarr ${inst.id} history error:`, err.message);
return { instance: inst.id, data: { records: [] } };
})
))),
timed('Radarr Tags', () => Promise.all(radarrInstances.map(inst =>
axios.get(`${inst.url}/api/v3/tag`, {
headers: { 'X-Api-Key': inst.apiKey }
}).then(res => ({ instance: inst.id, data: res.data })).catch(err => {
console.error(`[Poller] Radarr ${inst.id} tags error:`, err.message);
return { instance: inst.id, data: [] };
})
))),
timed('Sonarr Tags', async () => {
const tagsByType = await getTagsByType();
return tagsByType.sonarr || [];
}),
timed('Sonarr Queue', async () => {
const queuesByType = await getQueuesByType();
return queuesByType.sonarr || [];
}),
timed('Sonarr History', async () => {
const historyByType = await getHistoryByType({ pageSize: 10 });
return historyByType.sonarr || [];
}),
timed('Radarr Queue', async () => {
const queuesByType = await getQueuesByType();
return queuesByType.radarr || [];
}),
timed('Radarr History', async () => {
const historyByType = await getHistoryByType({ pageSize: 10 });
return historyByType.radarr || [];
}),
timed('Radarr Tags', async () => {
const tagsByType = await getTagsByType();
return tagsByType.radarr || [];
}),
]);
const [