diff --git a/data/configs/default.json b/data/configs/default.json index 11635a8b2..d690977bb 100644 --- a/data/configs/default.json +++ b/data/configs/default.json @@ -1,389 +1,393 @@ { - "schemaVersion": 1, - "configProperties": { - "name": "default" - }, - "categories": [ - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f", - "position": 1, - "name": "Welcome to Homarr 🎉", - "type": "category" - } - ], - "wrappers": [ - { - "id": "default", - "position": 0 - }, - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a326", - "position": 1 - } - ], - "apps": [ - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337", - "name": "Discord", - "url": "https://discord.com/invite/aCsmEV5RgA", - "behaviour": { - "onClickUrl": "https://discord.com/invite/aCsmEV5RgA", - "isOpeningNewTab": true, - "externalUrl": "https://discord.com/invite/aCsmEV5RgA" - }, - "network": { - "enabledStatusChecker": false, - "statusCodes": [ - "200" - ] - }, - "appearance": { - "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png" - }, - "integration": { - "type": null, - "properties": [] - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "md": { - "location": { - "x": 3, - "y": 1 - }, - "size": { - "width": 3, - "height": 1 - } - }, - "sm": { - "location": { - "x": 2, - "y": 1 - }, - "size": { - "width": 1, - "height": 1 - } - }, - "lg": { - "location": { - "x": 2, - "y": 1 - }, - "size": { - "width": 1, - "height": 1 - } - } - } - }, - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990", - "name": "Donate", - "url": "https://ko-fi.com/ajnart", - "behaviour": { - "onClickUrl": "https://ko-fi.com/ajnart", - "externalUrl": "https://ko-fi.com/ajnart", - "isOpeningNewTab": true - }, - "network": { - "enabledStatusChecker": false, - "statusCodes": [ - "200" - ] - }, - "appearance": { - "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png" - }, - "integration": { - "type": null, - "properties": [] - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "md": { - "location": { - "x": 2, - "y": 1 - }, - "size": { - "width": 1, - "height": 1 - } - }, - "sm": { - "location": { - "x": 2, - "y": 2 - }, - "size": { - "width": 1, - "height": 1 - } - }, - "lg": { - "location": { - "x": 3, - "y": 1 - }, - "size": { - "width": 1, - "height": 1 - } - } - } - }, - { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330", - "name": "Contribute", - "url": "https://github.com/ajnart/homarr", - "behaviour": { - "onClickUrl": "https://github.com/ajnart/homarr", - "externalUrl": "https://github.com/ajnart/homarr", - "isOpeningNewTab": true - }, - "network": { - "enabledStatusChecker": false, - "statusCodes": [] - }, - "appearance": { - "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png" - }, - "integration": { - "type": null, - "properties": [] - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "md": { - "location": { - "x": 2, - "y": 0 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "sm": { - "location": { - "x": 0, - "y": 2 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "lg": { - "location": { - "x": 4, - "y": 0 - }, - "size": { - "width": 2, - "height": 2 - } - } - } - }, - { - "id": "5df743d9-5cb1-457c-85d2-64ff86855652", - "name": "Documentation", - "url": "https://homarr.dev", - "behaviour": { - "onClickUrl": "https://homarr.dev", - "externalUrl": "https://homarr.dev", - "isOpeningNewTab": true - }, - "network": { - "enabledStatusChecker": false, - "statusCodes": [ - "200" - ] - }, - "appearance": { - "iconUrl": "/imgs/logo/logo.png" - }, - "integration": { - "type": null, - "properties": [] - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "md": { - "location": { - "x": 0, - "y": 1 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "sm": { - "location": { - "x": 0, - "y": 0 - }, - "size": { - "width": 1, - "height": 1 - } - }, - "lg": { - "location": { - "x": 0, - "y": 1 - }, - "size": { - "width": 2, - "height": 1 - } - } - } - } - ], - "widgets": [ - { - "id": "971aa859-8570-49a1-8d34-dd5c7b3638d1", - "type": "date", - "properties": { - "display24HourFormat": true - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "sm": { - "location": { - "x": 0, - "y": 1 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "md": { - "location": { - "x": 4, - "y": 0 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "lg": { - "location": { - "x": 2, - "y": 0 - }, - "size": { - "width": 2, - "height": 1 - } - } - } - }, - { - "id": "e3004052-6b83-480e-b458-56e8ccdca5f0", - "type": "weather", - "properties": { - "displayInFahrenheit": false, - "location": "Paris" - }, - "area": { - "type": "category", - "properties": { - "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" - } - }, - "shape": { - "md": { - "location": { - "x": 0, - "y": 0 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "sm": { - "location": { - "x": 1, - "y": 0 - }, - "size": { - "width": 2, - "height": 1 - } - }, - "lg": { - "location": { - "x": 0, - "y": 0 - }, - "size": { - "width": 2, - "height": 1 - } - } - } - } - ], - "settings": { - "common": { - "searchEngine": { - "type": "google", - "properties": {} - } - }, - "customization": { - "layout": { - "enabledLeftSidebar": false, - "enabledRightSidebar": false, - "enabledDocker": false, - "enabledPing": false, - "enabledSearchbar": true - }, - "pageTitle": "Homarr v0.12 ⭐️", - "logoImageUrl": "/imgs/logo/logo.png", - "faviconUrl": "/imgs/favicon/favicon-squared.png", - "backgroundImageUrl": "", - "customCss": "", - "colors": { - "primary": "red", - "secondary": "yellow", - "shade": 7 - }, - "appOpacity": 100 - } + "schemaVersion": 1, + "configProperties": { + "name": "default" + }, + "categories": [ + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f", + "position": 1, + "name": "Welcome to Homarr 🎉", + "type": "category" } + ], + "wrappers": [ + { + "id": "default", + "position": 0 + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a326", + "position": 1 + } + ], + "apps": [ + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337", + "name": "Discord", + "url": "https://discord.com/invite/aCsmEV5RgA", + "behaviour": { + "onClickUrl": "https://discord.com/invite/aCsmEV5RgA", + "isOpeningNewTab": true, + "externalUrl": "https://discord.com/invite/aCsmEV5RgA" + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [ + "200" + ] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png" + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "md": { + "location": { + "x": 3, + "y": 1 + }, + "size": { + "width": 3, + "height": 1 + } + }, + "sm": { + "location": { + "x": 2, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 2, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + } + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990", + "name": "Donate", + "url": "https://ko-fi.com/ajnart", + "behaviour": { + "onClickUrl": "https://ko-fi.com/ajnart", + "externalUrl": "https://ko-fi.com/ajnart", + "isOpeningNewTab": true + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [ + "200" + ] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png" + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "md": { + "location": { + "x": 2, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "sm": { + "location": { + "x": 2, + "y": 2 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 3, + "y": 1 + }, + "size": { + "width": 1, + "height": 1 + } + } + } + }, + { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330", + "name": "Contribute", + "url": "https://github.com/ajnart/homarr", + "behaviour": { + "onClickUrl": "https://github.com/ajnart/homarr", + "externalUrl": "https://github.com/ajnart/homarr", + "isOpeningNewTab": true + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [] + }, + "appearance": { + "iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png" + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "md": { + "location": { + "x": 2, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "sm": { + "location": { + "x": 0, + "y": 2 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "lg": { + "location": { + "x": 4, + "y": 0 + }, + "size": { + "width": 2, + "height": 2 + } + } + } + }, + { + "id": "5df743d9-5cb1-457c-85d2-64ff86855652", + "name": "Documentation", + "url": "https://homarr.dev", + "behaviour": { + "onClickUrl": "https://homarr.dev", + "externalUrl": "https://homarr.dev", + "isOpeningNewTab": true + }, + "network": { + "enabledStatusChecker": false, + "statusCodes": [ + "200" + ] + }, + "appearance": { + "iconUrl": "/imgs/logo/logo.png" + }, + "integration": { + "type": null, + "properties": [] + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "md": { + "location": { + "x": 0, + "y": 1 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "sm": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 1, + "height": 1 + } + }, + "lg": { + "location": { + "x": 0, + "y": 1 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + } + ], + "widgets": [ + { + "id": "971aa859-8570-49a1-8d34-dd5c7b3638d1", + "type": "date", + "properties": { + "display24HourFormat": true + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "sm": { + "location": { + "x": 0, + "y": 1 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "md": { + "location": { + "x": 4, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "lg": { + "location": { + "x": 2, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + }, + { + "id": "e3004052-6b83-480e-b458-56e8ccdca5f0", + "type": "weather", + "properties": { + "displayInFahrenheit": false, + "location": { + "name": "Paris", + "latitude": 48.85341, + "longitude": 2.3488 + } + }, + "area": { + "type": "category", + "properties": { + "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f" + } + }, + "shape": { + "md": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "sm": { + "location": { + "x": 1, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + }, + "lg": { + "location": { + "x": 0, + "y": 0 + }, + "size": { + "width": 2, + "height": 1 + } + } + } + } + ], + "settings": { + "common": { + "searchEngine": { + "type": "google", + "properties": {} + } + }, + "customization": { + "layout": { + "enabledLeftSidebar": false, + "enabledRightSidebar": false, + "enabledDocker": false, + "enabledPing": false, + "enabledSearchbar": true + }, + "pageTitle": "Homarr v0.12 ⭐️", + "logoImageUrl": "/imgs/logo/logo.png", + "faviconUrl": "/imgs/favicon/favicon-squared.png", + "backgroundImageUrl": "", + "customCss": "", + "colors": { + "primary": "red", + "secondary": "yellow", + "shade": 7 + }, + "appOpacity": 100 + } + } } diff --git a/src/pages/[slug].tsx b/src/pages/[slug].tsx index 9ce096887..52d2aaade 100644 --- a/src/pages/[slug].tsx +++ b/src/pages/[slug].tsx @@ -38,7 +38,7 @@ export async function getServerSideProps({ }; } - const config = getFrontendConfig(configName as string); + const config = await getFrontendConfig(configName as string); setCookie('config-name', configName, { req, res, diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 7ff04c4cb..d268021ae 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -47,7 +47,7 @@ export async function getServerSideProps({ } const translations = await getServerSideTranslations(dashboardNamespaces, locale, req, res); - const config = getFrontendConfig(configName as string); + const config = await getFrontendConfig(configName as string); return { props: { diff --git a/src/server/api/routers/weather.ts b/src/server/api/routers/weather.ts index acb85c9ea..b2d8c3ace 100644 --- a/src/server/api/routers/weather.ts +++ b/src/server/api/routers/weather.ts @@ -34,10 +34,7 @@ export const weatherRouter = createTRPCRouter({ results: z.array(citySchema), }) ) - .query(async ({ input }) => { - const res = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`); - return res.json(); - }), + .query(async ({ input }) => fetchCity(input.query)), at: publicProcedure .input( z.object({ @@ -56,3 +53,12 @@ export const weatherRouter = createTRPCRouter({ export type City = z.infer; export type Weather = z.infer; + +const outputSchema = z.object({ + results: z.array(citySchema), +}); + +export const fetchCity = async (query: string) => { + const res = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${query}`); + return outputSchema.parse(await res.json()); +}; diff --git a/src/tools/config/getFrontendConfig.ts b/src/tools/config/getFrontendConfig.ts index 376522d4c..0a4003697 100644 --- a/src/tools/config/getFrontendConfig.ts +++ b/src/tools/config/getFrontendConfig.ts @@ -1,10 +1,19 @@ import Consola from 'consola'; - -import { ConfigType } from '../../types/config'; +import fs from 'fs'; +import { BackendConfigType, ConfigType } from '../../types/config'; import { getConfig } from './getConfig'; +import { fetchCity } from '~/server/api/routers/weather'; -export const getFrontendConfig = (name: string): ConfigType => { - const config = getConfig(name); +export const getFrontendConfig = async (name: string): Promise => { + let config = getConfig(name); + + const anyWeatherWidgetWithStringLocation = config.widgets.some( + (widget) => widget.type === 'weather' && typeof widget.properties.location === 'string' + ); + + if (anyWeatherWidgetWithStringLocation) { + config = await migrateLocation(config); + } Consola.info(`Requested frontend content of configuration '${name}'`); // If not, return the config @@ -41,3 +50,39 @@ export const getFrontendConfig = (name: string): ConfigType => { })), }; }; + +const migrateLocation = async (config: BackendConfigType) => { + Consola.log('Migrating config file to new location schema...', config.configProperties.name); + + const configName = config.configProperties.name; + const migratedConfig = { + ...config, + widgets: await Promise.all( + config.widgets.map(async (widget) => + widget.type !== 'weather' || typeof widget.properties.location !== 'string' + ? widget + : { + ...widget, + properties: { + ...widget.properties, + location: await fetchCity(widget.properties.location) + .then(({ results }) => ({ + name: results[0].name, + latitude: results[0].latitude, + longitude: results[0].longitude, + })) + .catch(() => ({ + name: '', + latitude: 0, + longitude: 0, + })), + }, + } + ) + ), + }; + + fs.writeFileSync(`./data/configs/${configName}.json`, JSON.stringify(migratedConfig, null, 2)); + + return migratedConfig; +}; diff --git a/src/tools/config/migrateConfig.ts b/src/tools/config/migrateConfig.ts index defb78a26..b85e83e38 100644 --- a/src/tools/config/migrateConfig.ts +++ b/src/tools/config/migrateConfig.ts @@ -2,13 +2,11 @@ import Consola from 'consola'; import { v4 as uuidv4 } from 'uuid'; -import { Config, serviceItem } from '../types'; import { ConfigAppIntegrationType, ConfigAppType, IntegrationType } from '../../types/app'; import { AreaType } from '../../types/area'; import { CategoryType } from '../../types/category'; import { BackendConfigType } from '../../types/config'; import { SearchEngineCommonSettingsType } from '../../types/settings'; -import { IWidget } from '../../widgets/widgets'; import { ICalendarWidget } from '../../widgets/calendar/CalendarTile'; import { IDashDotTile } from '../../widgets/dashDot/DashDotTile'; import { IDateWidget } from '../../widgets/date/DateTile'; @@ -16,6 +14,8 @@ import { ITorrentNetworkTraffic } from '../../widgets/download-speed/TorrentNetw import { ITorrent } from '../../widgets/torrent/TorrentTile'; import { IUsenetWidget } from '../../widgets/useNet/UseNetTile'; import { IWeatherWidget } from '../../widgets/weather/WeatherTile'; +import { IWidget } from '../../widgets/widgets'; +import { Config, serviceItem } from '../types'; export function migrateConfig(config: Config): BackendConfigType { const newConfig: BackendConfigType = { @@ -208,7 +208,11 @@ const migrateModules = (config: Config): IWidget[] => { type: 'weather', properties: { displayInFahrenheit: oldModule.options?.freedomunit?.value ?? false, - location: oldModule.options?.location?.value ?? 'Paris', + location: { + name: oldModule.options?.location?.value ?? '', + latitude: 0, + longitude: 0, + }, }, area: { type: 'wrapper',