Merge branch 'dev' into add-more-openapi
This commit is contained in:
323 files changed
+4555
-2926
No files matched your search
@@ -1,9 +1,11 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { Duration } from "dayjs/plugin/duration";
|
||||
|
||||
import { logger } from "@homarr/log";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import type { createChannelWithLatestAndEvents } from "@homarr/redis";
|
||||
|
||||
const logger = createLogger({ module: "cachedRequestHandler" });
|
||||
|
||||
interface Options<TData, TInput extends Record<string, unknown>> {
|
||||
// Unique key for this request handler
|
||||
queryKey: string;
|
||||
@@ -34,9 +36,10 @@ export const createCachedRequestHandler = <TData, TInput extends Record<string,
|
||||
};
|
||||
|
||||
if (forceUpdate) {
|
||||
logger.debug(
|
||||
`Cached request handler forced update for channel='${channel.name}' queryKey='${options.queryKey}'`,
|
||||
);
|
||||
logger.debug("Cached request handler forced update", {
|
||||
channel: channel.name,
|
||||
queryKey: options.queryKey,
|
||||
});
|
||||
return await requestNewDataAsync();
|
||||
}
|
||||
|
||||
@@ -47,22 +50,27 @@ export const createCachedRequestHandler = <TData, TInput extends Record<string,
|
||||
dayjs().diff(channelData.timestamp, "milliseconds") > options.cacheDuration.asMilliseconds();
|
||||
|
||||
if (shouldRequestNewData) {
|
||||
logger.debug(
|
||||
`Cached request handler cache miss for channel='${channel.name}' queryKey='${options.queryKey}' reason='${!channelData ? "no data" : "cache expired"}'`,
|
||||
);
|
||||
logger.debug("Cached request handler cache miss", {
|
||||
channel: channel.name,
|
||||
queryKey: options.queryKey,
|
||||
reason: !channelData ? "no data" : "cache expired",
|
||||
});
|
||||
return await requestNewDataAsync();
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Cached request handler cache hit for channel='${channel.name}' queryKey='${options.queryKey}' expiresAt='${dayjs(channelData.timestamp).add(options.cacheDuration).toISOString()}'`,
|
||||
);
|
||||
logger.debug("Cached request handler cache hit", {
|
||||
channel: channel.name,
|
||||
queryKey: options.queryKey,
|
||||
expiresAt: dayjs(channelData.timestamp).add(options.cacheDuration).toISOString(),
|
||||
});
|
||||
|
||||
return channelData;
|
||||
},
|
||||
async invalidateAsync() {
|
||||
logger.debug(
|
||||
`Cached request handler invalidating cache channel='${channel.name}' queryKey='${options.queryKey}'`,
|
||||
);
|
||||
logger.debug("Cached request handler invalidating cache", {
|
||||
channel: channel.name,
|
||||
queryKey: options.queryKey,
|
||||
});
|
||||
await this.getCachedOrUpdatedDataAsync({ forceUpdate: true });
|
||||
},
|
||||
subscribe(callback: (data: TData) => void) {
|
||||
|
||||
@@ -3,10 +3,11 @@ import SuperJSON from "superjson";
|
||||
import { hashObjectBase64, Stopwatch } from "@homarr/common";
|
||||
import { decryptSecret } from "@homarr/common/server";
|
||||
import type { MaybeArray } from "@homarr/common/types";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
||||
import { db } from "@homarr/db";
|
||||
import { getItemsWithIntegrationsAsync, getServerSettingsAsync } from "@homarr/db/queries";
|
||||
import type { WidgetKind } from "@homarr/definitions";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
// This imports are done that way to avoid circular dependencies.
|
||||
import type { inferSupportedIntegrationsStrict } from "../../../widgets/src";
|
||||
@@ -14,6 +15,8 @@ import { reduceWidgetOptionsWithDefaultValues } from "../../../widgets/src";
|
||||
import type { WidgetComponentProps } from "../../../widgets/src/definition";
|
||||
import type { createCachedIntegrationRequestHandler } from "./cached-integration-request-handler";
|
||||
|
||||
const logger = createLogger({ module: "cachedRequestIntegrationJobHandler" });
|
||||
|
||||
export const createRequestIntegrationJobHandler = <
|
||||
TWidgetKind extends WidgetKind,
|
||||
TIntegrationKind extends inferSupportedIntegrationsStrict<TWidgetKind>,
|
||||
@@ -37,9 +40,10 @@ export const createRequestIntegrationJobHandler = <
|
||||
kinds: widgetKinds,
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Found items for integration widgetKinds='${widgetKinds.join(",")}' count=${itemsForIntegration.length}`,
|
||||
);
|
||||
logger.debug("Found items for integration", {
|
||||
widgetKinds: widgetKinds.join(","),
|
||||
count: itemsForIntegration.length,
|
||||
});
|
||||
|
||||
const distinctIntegrations: {
|
||||
integrationId: string;
|
||||
@@ -97,14 +101,14 @@ export const createRequestIntegrationJobHandler = <
|
||||
);
|
||||
const stopWatch = new Stopwatch();
|
||||
await innerHandler.getCachedOrUpdatedDataAsync({ forceUpdate: true });
|
||||
logger.debug(
|
||||
`Ran integration job integration=${integrationId} inputHash='${inputHash}' elapsed=${stopWatch.getElapsedInHumanWords()}`,
|
||||
);
|
||||
logger.debug("Ran integration job", {
|
||||
integration: integrationId,
|
||||
inputHash,
|
||||
elapsed: stopWatch.getElapsedInHumanWords(),
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
new Error(`Failed to run integration job integration=${integrationId} inputHash='${inputHash}'`, {
|
||||
cause: error,
|
||||
}),
|
||||
new ErrorWithMetadata("Failed to run integration job", { integrationId, inputHash }, { cause: error }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -11,7 +11,7 @@ export const minecraftServerStatusRequestHandler = createCachedWidgetRequestHand
|
||||
async requestAsync(input: { domain: string; isBedrockServer: boolean }) {
|
||||
const path = `${input.isBedrockServer ? "/bedrock" : ""}/3/${input.domain}`;
|
||||
|
||||
const response = await fetchWithTimeout(`https://api.mcsrvstat.us${path}`);
|
||||
const response = await fetchWithTimeoutAsync(`https://api.mcsrvstat.us${path}`);
|
||||
return responseSchema.parse(await response.json());
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
|
||||
@@ -4,10 +4,12 @@ import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import type { Modify } from "@homarr/common/types";
|
||||
import { logger } from "@homarr/log";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
const logger = createLogger({ module: "rssFeedsRequestHandler" });
|
||||
|
||||
export const rssFeedsRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "rssFeedList",
|
||||
widgetKind: "rssFeed",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,7 +9,7 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "fetchStockPriceResult",
|
||||
widgetKind: "stockPrice",
|
||||
async requestAsync(input: { stock: string; timeRange: string; timeInterval: string }) {
|
||||
const response = await fetchWithTimeout(
|
||||
const response = await fetchWithTimeoutAsync(
|
||||
`https://query1.finance.yahoo.com/v8/finance/chart/${input.stock}?range=${input.timeRange}&interval=${input.timeInterval}`,
|
||||
);
|
||||
const data = dataSchema.parse(await response.json());
|
||||
@@ -24,12 +24,16 @@ export const fetchStockPriceHandler = createCachedWidgetRequestHandler({
|
||||
if (!firstResult) {
|
||||
throw new Error("Received invalid data");
|
||||
}
|
||||
|
||||
const priceHistory =
|
||||
firstResult.indicators.quote[0]?.close.filter(
|
||||
// Filter out null values from price arrays (Yahoo Finance returns null for missing data points)
|
||||
(value) => value !== null && value !== undefined,
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
priceHistory:
|
||||
firstResult.indicators.quote[0]?.close.filter(
|
||||
// Filter out null values from price arrays (Yahoo Finance returns null for missing data points)
|
||||
(value) => value !== null && value !== undefined,
|
||||
) ?? [],
|
||||
priceHistory,
|
||||
previousClose: firstResult.meta.previousClose ?? priceHistory[0] ?? 1,
|
||||
symbol: firstResult.meta.symbol,
|
||||
shortName: firstResult.meta.shortName,
|
||||
};
|
||||
@@ -58,6 +62,7 @@ const dataSchema = z
|
||||
meta: z.object({
|
||||
symbol: z.string(),
|
||||
shortName: z.string(),
|
||||
previousClose: z.number().optional(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -2,14 +2,16 @@ import dayjs from "dayjs";
|
||||
import { Octokit } from "octokit";
|
||||
import { compareSemVer, isValidSemVer } from "semver-parser";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { env } from "@homarr/common/env";
|
||||
import { logger } from "@homarr/log";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { createChannelWithLatestAndEvents } from "@homarr/redis";
|
||||
import { createCachedRequestHandler } from "@homarr/request-handler/lib/cached-request-handler";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
|
||||
const logger = createLogger({ module: "updateCheckerRequestHandler" });
|
||||
|
||||
export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
queryKey: "homarr-update-checker",
|
||||
cacheDuration: dayjs.duration(1, "hour"),
|
||||
@@ -21,7 +23,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
|
||||
const octokit = new Octokit({
|
||||
request: {
|
||||
fetch: fetchWithTimeout,
|
||||
fetch: fetchWithTimeoutAsync,
|
||||
},
|
||||
});
|
||||
const releases = await octokit.rest.repos.listReleases({
|
||||
@@ -34,7 +36,7 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
|
||||
for (const release of releases.data) {
|
||||
if (!isValidSemVer(release.tag_name)) {
|
||||
logger.warn(`Unable to parse semantic tag '${release.tag_name}'. Update check might not work.`);
|
||||
logger.warn("Unable to parse semantic tag. Update check might not work.", { tagName: release.tag_name });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -46,11 +48,12 @@ export const updateCheckerRequestHandler = createCachedRequestHandler({
|
||||
.sort((releaseA, releaseB) => compareSemVer(releaseB.tag_name, releaseA.tag_name));
|
||||
if (availableNewerReleases.length > 0) {
|
||||
logger.info(
|
||||
"Update checker found a new available version",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
`Update checker found a new available version: ${availableReleases[0]!.tag_name}. Current version is ${currentVersion}`,
|
||||
{ version: availableReleases[0]!.tag_name, currentVersion },
|
||||
);
|
||||
} else {
|
||||
logger.debug(`Update checker did not find any available updates. Current version is ${currentVersion}`);
|
||||
logger.debug("Update checker did not find any available updates", { currentVersion });
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { z } from "zod";
|
||||
|
||||
import { fetchWithTimeout } from "@homarr/common";
|
||||
import { fetchWithTimeoutAsync } from "@homarr/core/infrastructure/http/timeout";
|
||||
|
||||
import { createCachedWidgetRequestHandler } from "./lib/cached-widget-request-handler";
|
||||
|
||||
@@ -9,7 +9,7 @@ export const weatherRequestHandler = createCachedWidgetRequestHandler({
|
||||
queryKey: "weatherAtLocation",
|
||||
widgetKind: "weather",
|
||||
async requestAsync(input: { latitude: number; longitude: number }) {
|
||||
const res = await fetchWithTimeout(
|
||||
const res = await fetchWithTimeoutAsync(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||
);
|
||||
const json: unknown = await res.json();
|
||||
|
||||
Reference in new issue
Block a user