mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-09 06:55:51 +01:00
🏗️ Migrate calendar to tRPC
This commit is contained in:
@@ -11,6 +11,7 @@ import { mediaRequestsRouter } from './routers/media-request';
|
|||||||
import { mediaServerRouter } from './routers/media-server';
|
import { mediaServerRouter } from './routers/media-server';
|
||||||
import { overseerrRouter } from './routers/overseerr';
|
import { overseerrRouter } from './routers/overseerr';
|
||||||
import { usenetRouter } from './routers/usenet/route';
|
import { usenetRouter } from './routers/usenet/route';
|
||||||
|
import { calendarRouter } from './routers/calendar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -30,6 +31,7 @@ export const rootRouter = createTRPCRouter({
|
|||||||
mediaServer: mediaServerRouter,
|
mediaServer: mediaServerRouter,
|
||||||
overseerr: overseerrRouter,
|
overseerr: overseerrRouter,
|
||||||
usenet: usenetRouter,
|
usenet: usenetRouter,
|
||||||
|
calendar: calendarRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
95
src/server/api/routers/calendar.ts
Normal file
95
src/server/api/routers/calendar.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import Consola from 'consola';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { getConfig } from '~/tools/config/getConfig';
|
||||||
|
import { AppIntegrationType } from '~/types/app';
|
||||||
|
import { createTRPCRouter, publicProcedure } from '../trpc';
|
||||||
|
|
||||||
|
export const calendarRouter = createTRPCRouter({
|
||||||
|
medias: publicProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
configName: z.string(),
|
||||||
|
month: z.number().min(1).max(12),
|
||||||
|
year: z.number().min(1900).max(2300),
|
||||||
|
options: z.object({
|
||||||
|
useSonarrv4: z.boolean().optional().default(false),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { configName, month, year, options } = input;
|
||||||
|
const config = getConfig(configName);
|
||||||
|
|
||||||
|
const mediaAppIntegrationTypes: AppIntegrationType['type'][] = [
|
||||||
|
'sonarr',
|
||||||
|
'radarr',
|
||||||
|
'readarr',
|
||||||
|
'lidarr',
|
||||||
|
];
|
||||||
|
const mediaApps = config.apps.filter(
|
||||||
|
(app) => app.integration && mediaAppIntegrationTypes.includes(app.integration.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
const integrationTypeEndpointMap = new Map<AppIntegrationType['type'], string>([
|
||||||
|
['sonarr', input.options.useSonarrv4 ? '/api/v3/calendar' : '/api/calendar'],
|
||||||
|
['radarr', '/api/v3/calendar'],
|
||||||
|
['lidarr', '/api/v1/calendar'],
|
||||||
|
['readarr', '/api/v1/calendar'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const promises = 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 medias = await Promise.all(promises);
|
||||||
|
|
||||||
|
const countFailed = medias.filter((x) => !x.success).length;
|
||||||
|
if (countFailed > 0) {
|
||||||
|
Consola.warn(`A total of ${countFailed} apps for the calendar widget failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { useMantineTheme } from '@mantine/core';
|
import { useMantineTheme } from '@mantine/core';
|
||||||
import { Calendar } from '@mantine/dates';
|
import { Calendar } from '@mantine/dates';
|
||||||
import { IconCalendarTime } from '@tabler/icons-react';
|
import { IconCalendarTime } from '@tabler/icons-react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { i18n } from 'next-i18next';
|
import { i18n } from 'next-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { api } from '~/utils/api';
|
||||||
|
import { useEditModeStore } from '../../components/Dashboard/Views/useEditModeStore';
|
||||||
import { useConfigContext } from '../../config/provider';
|
import { useConfigContext } from '../../config/provider';
|
||||||
import { defineWidget } from '../helper';
|
import { defineWidget } from '../helper';
|
||||||
import { IWidget } from '../widgets';
|
import { IWidget } from '../widgets';
|
||||||
import { CalendarDay } from './CalendarDay';
|
import { CalendarDay } from './CalendarDay';
|
||||||
import { getBgColorByDateAndTheme } from './bg-calculator';
|
import { getBgColorByDateAndTheme } from './bg-calculator';
|
||||||
import { MediasType } from './type';
|
import { MediasType } from './type';
|
||||||
import { useEditModeStore } from '../../components/Dashboard/Views/useEditModeStore';
|
|
||||||
|
|
||||||
const definition = defineWidget({
|
const definition = defineWidget({
|
||||||
id: 'calendar',
|
id: 'calendar',
|
||||||
@@ -55,22 +55,18 @@ function CalendarTile({ widget }: CalendarTileProps) {
|
|||||||
const [month, setMonth] = useState(new Date());
|
const [month, setMonth] = useState(new Date());
|
||||||
const isEditMode = useEditModeStore((x) => x.enabled);
|
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||||
|
|
||||||
const { data: medias } = useQuery({
|
const { data: medias } = api.calendar.medias.useQuery(
|
||||||
queryKey: [
|
{
|
||||||
'calendar/medias',
|
configName: configName!,
|
||||||
{ month: month.getMonth(), year: month.getFullYear(), v4: widget.properties.useSonarrv4 },
|
month: month.getMonth() + 1,
|
||||||
],
|
year: month.getFullYear(),
|
||||||
staleTime: 1000 * 60 * 60 * 5,
|
options: { useSonarrv4: widget.properties.useSonarrv4 },
|
||||||
enabled: isEditMode === false,
|
},
|
||||||
queryFn: async () =>
|
{
|
||||||
(await (
|
staleTime: 1000 * 60 * 60 * 5,
|
||||||
await fetch(
|
enabled: isEditMode === false,
|
||||||
`/api/modules/calendar?year=${month.getFullYear()}&month=${
|
}
|
||||||
month.getMonth() + 1
|
);
|
||||||
}&configName=${configName}&widgetId=${widget.id}`
|
|
||||||
)
|
|
||||||
).json()) as MediasType,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Calendar
|
<Calendar
|
||||||
|
|||||||
Reference in New Issue
Block a user