Files
Homarr/src/pages/api/modules/calendar.ts
2023-06-10 19:04:54 +02:00

139 lines
4.4 KiB
TypeScript

import axios from 'axios';
import Consola from 'consola';
import { NextApiRequest, NextApiResponse } from 'next';
import { z } from 'zod';
import { AppIntegrationType, IntegrationType } from '../../../types/app';
import { getConfig } from '../../../tools/config/getConfig';
import { checkIntegrationsType } from '~/tools/client/app-properties';
export default async (req: NextApiRequest, res: NextApiResponse) => {
// Filter out if the reuqest is a POST or a GET
if (req.method === 'GET') {
return Get(req, res);
}
return res.status(405).json({
statusCode: 405,
message: 'Method not allowed',
});
};
const getQuerySchema = z.object({
month: z
.string()
.regex(/^\d+$/)
.transform((x) => parseInt(x, 10)),
year: z
.string()
.regex(/^\d+$/)
.transform((x) => parseInt(x, 10)),
widgetId: z.string().uuid(),
configName: z.string(),
});
async function Get(req: NextApiRequest, res: NextApiResponse) {
const parseResult = getQuerySchema.safeParse(req.query);
if (!parseResult.success) {
return res.status(400).json({
statusCode: 400,
message: 'Invalid query parameters, please specify the widgetId, month, year and configName',
});
}
// Parse req.body as a AppItem
const { month, year, widgetId, configName } = parseResult.data;
const config = getConfig(configName);
// Find the calendar widget in the config
const calendar = config.widgets.find((w) => w.type === 'calendar' && w.id === widgetId);
const useSonarrv4 = calendar?.properties.useSonarrv4 ?? false;
const mediaAppIntegrationTypes = [
'sonarr',
'radarr',
'readarr',
'lidarr',
] as const satisfies readonly IntegrationType[];
const mediaApps = config.apps.filter((app) =>
checkIntegrationsType(app.integration, mediaAppIntegrationTypes)
);
const IntegrationTypeEndpointMap = new Map<AppIntegrationType['type'], string>([
['sonarr', useSonarrv4 ? '/api/v3/calendar' : '/api/calendar'],
['radarr', '/api/v3/calendar'],
['lidarr', '/api/v1/calendar'],
['readarr', '/api/v1/calendar'],
]);
try {
const medias = await Promise.all(
await mediaApps.map(async (app) => {
const integration = app.integration!;
const endpoint = IntegrationTypeEndpointMap.get(integration.type);
if (!endpoint) {
return {
type: integration.type,
items: [],
success: false,
};
}
// Get the origin URL
let { href: origin } = new URL(app.url);
if (origin.endsWith('/')) {
origin = origin.slice(0, -1);
}
const start = new Date(year, month - 1, 1); // First day of month
const end = new Date(year, month, 0); // Last day of month
const apiKey = integration.properties.find((x) => x.field === 'apiKey')?.value;
if (!apiKey) return { type: integration.type, items: [], success: false };
return axios
.get(
`${origin}${endpoint}?apiKey=${apiKey}&end=${end.toISOString()}&start=${start.toISOString()}&includeSeries=true&includeEpisodeFile=true&includeEpisodeImages=true`
)
.then((x) => ({ type: integration.type, items: x.data as any[], success: true }))
.catch((err) => {
Consola.error(
`failed to process request to app '${integration.type}' (${app.id}): ${err}`
);
return {
type: integration.type,
items: [],
success: false,
};
});
})
);
const results = await Promise.all(medias);
const countFailed = results.filter((x) => !x.success).length;
if (countFailed > 0) {
Consola.warn(`A total of ${countFailed} apps for the calendar widget failed`);
}
return res.status(200).json({
tvShows: medias.filter((m) => m.type === 'sonarr').flatMap((m) => m.items),
movies: medias.filter((m) => m.type === 'radarr').flatMap((m) => m.items),
books: medias.filter((m) => m.type === 'readarr').flatMap((m) => m.items),
musics: medias.filter((m) => m.type === 'lidarr').flatMap((m) => m.items),
totalCount: medias.reduce((p, c) => p + c.items.length, 0),
});
} catch (error) {
Consola.error(`Error while requesting media from your app. Check your configuration. ${error}`);
return res.status(500).json({
tvShows: [],
movies: [],
books: [],
musics: [],
totalCount: 0,
});
}
}