feat: add onboarding with oldmarr import (#1606)
This commit is contained in:
27
packages/old-import/src/mappers/map-app.ts
Normal file
27
packages/old-import/src/mappers/map-app.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { InferSelectModel } from "@homarr/db";
|
||||
import type { apps } from "@homarr/db/schema/sqlite";
|
||||
import type { OldmarrApp } from "@homarr/old-schema";
|
||||
|
||||
import type { OldmarrBookmarkDefinition } from "../widgets/definitions/bookmark";
|
||||
|
||||
export const mapOldmarrApp = (app: OldmarrApp): InferSelectModel<typeof apps> => {
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
iconUrl: app.appearance.iconUrl,
|
||||
description: app.behaviour.tooltipDescription ?? null,
|
||||
href: app.behaviour.externalUrl || app.url,
|
||||
};
|
||||
};
|
||||
|
||||
export const mapOldmarrBookmarkApp = (
|
||||
app: OldmarrBookmarkDefinition["options"]["items"][number],
|
||||
): InferSelectModel<typeof apps> => {
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
iconUrl: app.iconUrl,
|
||||
description: null,
|
||||
href: app.href,
|
||||
};
|
||||
};
|
||||
27
packages/old-import/src/mappers/map-board.ts
Normal file
27
packages/old-import/src/mappers/map-board.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import type { boards } from "@homarr/db/schema/sqlite";
|
||||
|
||||
import type { prepareMultipleImports } from "../prepare/prepare-multiple";
|
||||
import { mapColor } from "./map-colors";
|
||||
import { mapColumnCount } from "./map-column-count";
|
||||
|
||||
type PreparedBoard = ReturnType<typeof prepareMultipleImports>["preparedBoards"][number];
|
||||
|
||||
export const mapBoard = (preparedBoard: PreparedBoard): InferInsertModel<typeof boards> => ({
|
||||
id: createId(),
|
||||
name: preparedBoard.name,
|
||||
backgroundImageAttachment: preparedBoard.config.settings.customization.backgroundImageAttachment,
|
||||
backgroundImageUrl: preparedBoard.config.settings.customization.backgroundImageUrl,
|
||||
backgroundImageRepeat: preparedBoard.config.settings.customization.backgroundImageRepeat,
|
||||
backgroundImageSize: preparedBoard.config.settings.customization.backgroundImageSize,
|
||||
columnCount: mapColumnCount(preparedBoard.config, preparedBoard.size),
|
||||
faviconImageUrl: preparedBoard.config.settings.customization.faviconUrl,
|
||||
isPublic: preparedBoard.config.settings.access.allowGuests,
|
||||
logoImageUrl: preparedBoard.config.settings.customization.logoImageUrl,
|
||||
pageTitle: preparedBoard.config.settings.customization.pageTitle,
|
||||
metaTitle: preparedBoard.config.settings.customization.metaTitle,
|
||||
opacity: preparedBoard.config.settings.customization.appOpacity,
|
||||
primaryColor: mapColor(preparedBoard.config.settings.customization.colors.primary, "#fa5252"),
|
||||
secondaryColor: mapColor(preparedBoard.config.settings.customization.colors.secondary, "#fd7e14"),
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OldmarrConfig } from "@homarr/old-schema";
|
||||
import type { OldmarrImportConfiguration } from "@homarr/validation";
|
||||
|
||||
import type { OldmarrImportConfiguration } from "../settings";
|
||||
|
||||
export const mapColumnCount = (old: OldmarrConfig, screenSize: OldmarrImportConfiguration["screenSize"]) => {
|
||||
switch (screenSize) {
|
||||
|
||||
60
packages/old-import/src/mappers/map-integration.ts
Normal file
60
packages/old-import/src/mappers/map-integration.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { decryptSecretWithKey } from "@homarr/common/server";
|
||||
import { createId } from "@homarr/db";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import type { OldmarrIntegrationType } from "@homarr/old-schema";
|
||||
|
||||
import type { PreparedIntegration } from "../prepare/prepare-integrations";
|
||||
|
||||
export const mapIntegrationType = (type: OldmarrIntegrationType) => {
|
||||
const kind = mapping[type];
|
||||
if (!kind) {
|
||||
throw new Error(`Integration type ${type} is not supported yet`);
|
||||
}
|
||||
return kind;
|
||||
};
|
||||
|
||||
const mapping: Record<OldmarrIntegrationType, IntegrationKind | null> = {
|
||||
adGuardHome: "adGuardHome",
|
||||
deluge: "deluge",
|
||||
homeAssistant: "homeAssistant",
|
||||
jellyfin: "jellyfin",
|
||||
jellyseerr: "jellyseerr",
|
||||
lidarr: "lidarr",
|
||||
nzbGet: "nzbGet",
|
||||
openmediavault: "openmediavault",
|
||||
overseerr: "overseerr",
|
||||
pihole: "piHole",
|
||||
prowlarr: "prowlarr",
|
||||
proxmox: null,
|
||||
qBittorrent: "qBittorrent",
|
||||
radarr: "radarr",
|
||||
readarr: "readarr",
|
||||
sabnzbd: "sabNzbd",
|
||||
sonarr: "sonarr",
|
||||
tdarr: null,
|
||||
transmission: "transmission",
|
||||
plex: "plex",
|
||||
};
|
||||
|
||||
export const mapAndDecryptIntegrations = (
|
||||
preparedIntegrations: PreparedIntegration[],
|
||||
encryptionToken: string | null,
|
||||
) => {
|
||||
if (encryptionToken === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const key = Buffer.from(encryptionToken, "hex");
|
||||
|
||||
return preparedIntegrations.map(({ type, name, url, properties }) => ({
|
||||
id: createId(),
|
||||
name,
|
||||
url,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
kind: mapIntegrationType(type!),
|
||||
secrets: properties.map((property) => ({
|
||||
...property,
|
||||
value: property.value ? decryptSecretWithKey(property.value as `${string}.${string}`, key) : null,
|
||||
})),
|
||||
}));
|
||||
};
|
||||
89
packages/old-import/src/mappers/map-item.ts
Normal file
89
packages/old-import/src/mappers/map-item.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import type { items } from "@homarr/db/schema/sqlite";
|
||||
import { logger } from "@homarr/log";
|
||||
import type { BoardSize, OldmarrApp, OldmarrWidget } from "@homarr/old-schema";
|
||||
|
||||
import type { WidgetComponentProps } from "../../../widgets/src/definition";
|
||||
import { mapKind } from "../widgets/definitions";
|
||||
import { mapOptions } from "../widgets/options";
|
||||
|
||||
export const mapApp = (
|
||||
app: OldmarrApp,
|
||||
boardSize: BoardSize,
|
||||
appsMap: Map<string, { id: string }>,
|
||||
sectionMap: Map<string, { id: string }>,
|
||||
): InferInsertModel<typeof items> => {
|
||||
if (app.area.type === "sidebar") throw new Error("Mapping app in sidebar is not supported");
|
||||
|
||||
const shapeForSize = app.shape[boardSize];
|
||||
if (!shapeForSize) {
|
||||
throw new Error(`Failed to find a shape for appId='${app.id}' screenSize='${boardSize}'`);
|
||||
}
|
||||
|
||||
const sectionId = sectionMap.get(app.area.properties.id)?.id;
|
||||
if (!sectionId) {
|
||||
throw new Error(`Failed to find section for app appId='${app.id}' sectionId='${app.area.properties.id}'`);
|
||||
}
|
||||
|
||||
return {
|
||||
id: createId(),
|
||||
sectionId,
|
||||
height: shapeForSize.size.height,
|
||||
width: shapeForSize.size.width,
|
||||
xOffset: shapeForSize.location.x,
|
||||
yOffset: shapeForSize.location.y,
|
||||
kind: "app",
|
||||
options: SuperJSON.stringify({
|
||||
// it's safe to assume that the app exists in the map
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
appId: appsMap.get(app.id)?.id!,
|
||||
openInNewTab: app.behaviour.isOpeningNewTab,
|
||||
pingEnabled: app.network.enabledStatusChecker,
|
||||
showDescriptionTooltip: app.behaviour.tooltipDescription !== "",
|
||||
showTitle: app.appearance.appNameStatus === "normal",
|
||||
} satisfies WidgetComponentProps<"app">["options"]),
|
||||
};
|
||||
};
|
||||
|
||||
export const mapWidget = (
|
||||
widget: OldmarrWidget,
|
||||
boardSize: BoardSize,
|
||||
appsMap: Map<string, { id: string }>,
|
||||
sectionMap: Map<string, { id: string }>,
|
||||
): InferInsertModel<typeof items> | null => {
|
||||
if (widget.area.type === "sidebar") throw new Error("Mapping widget in sidebar is not supported");
|
||||
|
||||
const shapeForSize = widget.shape[boardSize];
|
||||
if (!shapeForSize) {
|
||||
throw new Error(`Failed to find a shape for widgetId='${widget.id}' screenSize='${boardSize}'`);
|
||||
}
|
||||
|
||||
const kind = mapKind(widget.type);
|
||||
if (!kind) {
|
||||
logger.warn(`Failed to map widget type='${widget.type}'. It's no longer supported`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const sectionId = sectionMap.get(widget.area.properties.id)?.id;
|
||||
if (!sectionId) {
|
||||
throw new Error(
|
||||
`Failed to find section for widget widgetId='${widget.id}' sectionId='${widget.area.properties.id}'`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: createId(),
|
||||
sectionId,
|
||||
height: shapeForSize.size.height,
|
||||
width: shapeForSize.size.width,
|
||||
xOffset: shapeForSize.location.x,
|
||||
yOffset: shapeForSize.location.y,
|
||||
kind,
|
||||
options: SuperJSON.stringify(
|
||||
mapOptions(kind, widget.properties, new Map([...appsMap.entries()].map(([key, value]) => [key, value.id]))),
|
||||
),
|
||||
};
|
||||
};
|
||||
24
packages/old-import/src/mappers/map-section.ts
Normal file
24
packages/old-import/src/mappers/map-section.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import type { sections } from "@homarr/db/schema/sqlite";
|
||||
import type { OldmarrCategorySection, OldmarrEmptySection } from "@homarr/old-schema";
|
||||
|
||||
export const mapCategorySection = (
|
||||
boardId: string,
|
||||
category: OldmarrCategorySection,
|
||||
): InferInsertModel<typeof sections> => ({
|
||||
id: createId(),
|
||||
boardId,
|
||||
kind: "category",
|
||||
xOffset: 0,
|
||||
yOffset: category.position,
|
||||
name: category.name,
|
||||
});
|
||||
|
||||
export const mapEmptySection = (boardId: string, wrapper: OldmarrEmptySection): InferInsertModel<typeof sections> => ({
|
||||
id: createId(),
|
||||
boardId,
|
||||
kind: "empty",
|
||||
xOffset: 0,
|
||||
yOffset: wrapper.position,
|
||||
});
|
||||
35
packages/old-import/src/mappers/map-user.ts
Normal file
35
packages/old-import/src/mappers/map-user.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { decryptSecretWithKey } from "@homarr/common/server";
|
||||
import type { InferInsertModel } from "@homarr/db";
|
||||
import { createId } from "@homarr/db";
|
||||
import type { users } from "@homarr/db/schema/sqlite";
|
||||
|
||||
import type { OldmarrImportUser } from "../user-schema";
|
||||
|
||||
export const mapAndDecryptUsers = (importUsers: OldmarrImportUser[], encryptionToken: string | null) => {
|
||||
if (encryptionToken === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const key = Buffer.from(encryptionToken, "hex");
|
||||
|
||||
return importUsers.map(
|
||||
({
|
||||
id,
|
||||
password,
|
||||
salt,
|
||||
settings,
|
||||
...user
|
||||
}): InferInsertModel<typeof users> & { oldId: string; isAdmin: boolean } => ({
|
||||
...user,
|
||||
oldId: id,
|
||||
id: createId(),
|
||||
colorScheme: settings?.colorScheme === "environment" ? undefined : settings?.colorScheme,
|
||||
firstDayOfWeek: settings?.firstDayOfWeek === "sunday" ? 0 : settings?.firstDayOfWeek === "monday" ? 1 : 6,
|
||||
provider: "credentials",
|
||||
pingIconsEnabled: settings?.replacePingWithIcons,
|
||||
isAdmin: user.isAdmin || user.isOwner,
|
||||
password: decryptSecretWithKey(password, key),
|
||||
salt: decryptSecretWithKey(salt, key),
|
||||
}),
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user