Files
homarr/packages/old-import/src/import-apps.ts
2024-12-19 16:10:22 +01:00

158 lines
4.6 KiB
TypeScript

import { createId, inArray } from "@homarr/db";
import type { Database, InferInsertModel, InferSelectModel } from "@homarr/db";
import { apps as appsTable } from "@homarr/db/schema";
import { logger } from "@homarr/log";
import type { OldmarrApp } from "@homarr/old-schema";
import type { BookmarkApp } from "./widgets/definitions/bookmark";
type DbAppWithoutId = Omit<InferSelectModel<typeof appsTable>, "id">;
interface AppMapping extends DbAppWithoutId {
ids: string[];
newId: string;
exists: boolean;
}
export const insertAppsAsync = async (
db: Database,
apps: OldmarrApp[],
bookmarkApps: BookmarkApp[],
distinctAppsByHref: boolean,
configName: string,
) => {
logger.info(
`Importing old homarr apps configuration=${configName} distinctAppsByHref=${distinctAppsByHref} apps=${apps.length}`,
);
const existingAppsWithHref = distinctAppsByHref
? await db.query.apps.findMany({
where: inArray(appsTable.href, [
...new Set(apps.map((app) => app.url).concat(bookmarkApps.map((app) => app.href))),
]),
})
: [];
logger.debug(`Found existing apps with href count=${existingAppsWithHref.length}`);
// Generate mappings for all apps from old to new ids
const appMappings: AppMapping[] = [];
addMappingFor(apps, appMappings, existingAppsWithHref, convertApp);
addMappingFor(bookmarkApps, appMappings, existingAppsWithHref, convertBookmarkApp);
logger.debug(`Mapping apps count=${appMappings.length}`);
const appsToCreate = appMappings
.filter((app) => !app.exists)
.map(
(app) =>
({
id: app.newId,
name: app.name,
iconUrl: app.iconUrl,
href: app.href,
description: app.description,
}) satisfies InferInsertModel<typeof appsTable>,
);
logger.debug(`Creating apps count=${appsToCreate.length}`);
if (appsToCreate.length > 0) {
await db.insert(appsTable).values(appsToCreate);
}
logger.info(`Imported apps count=${appsToCreate.length}`);
// Generates a map from old key to new key for all apps
return new Map(
appMappings
.map((app) => app.ids.map((id) => ({ id, newId: app.newId })))
.flat()
.map(({ id, newId }) => [id, newId]),
);
};
/**
* Creates a callback to be used in a find method that compares the old app with the new app
* @param app either an oldmarr app or a bookmark app
* @param convertApp a function that converts the app to a new app
* @returns a callback that compares the old app with the new app and returns true if they are the same
*/
const createFindCallback = <TApp extends OldmarrApp | BookmarkApp>(
app: TApp,
convertApp: (app: TApp) => DbAppWithoutId,
) => {
const oldApp = convertApp(app);
return (dbApp: DbAppWithoutId) =>
oldApp.href === dbApp.href &&
oldApp.name === dbApp.name &&
oldApp.iconUrl === dbApp.iconUrl &&
oldApp.description === dbApp.description;
};
/**
* Adds mappings for the given apps to the appMappings array
* @param apps apps to add mappings for
* @param appMappings existing app mappings
* @param existingAppsWithHref existing apps with href
* @param convertApp a function that converts the app to a new app
*/
const addMappingFor = <TApp extends OldmarrApp | BookmarkApp>(
apps: TApp[],
appMappings: AppMapping[],
existingAppsWithHref: InferSelectModel<typeof appsTable>[],
convertApp: (app: TApp) => DbAppWithoutId,
) => {
for (const app of apps) {
const previous = appMappings.find(createFindCallback(app, convertApp));
if (previous) {
previous.ids.push(app.id);
continue;
}
const existing = existingAppsWithHref.find(createFindCallback(app, convertApp));
if (existing) {
appMappings.push({
ids: [app.id],
newId: existing.id,
name: existing.name,
href: existing.href,
iconUrl: existing.iconUrl,
description: existing.description,
exists: true,
});
continue;
}
appMappings.push({
ids: [app.id],
newId: createId(),
...convertApp(app),
exists: false,
});
}
};
/**
* Converts an oldmarr app to a new app
* @param app oldmarr app
* @returns new app
*/
const convertApp = (app: OldmarrApp): DbAppWithoutId => ({
name: app.name,
href: app.behaviour.externalUrl === "" ? app.url : app.behaviour.externalUrl,
iconUrl: app.appearance.iconUrl,
description: app.behaviour.tooltipDescription ?? null,
});
/**
* Converts a bookmark app to a new app
* @param app bookmark app
* @returns new app
*/
const convertBookmarkApp = (app: BookmarkApp): DbAppWithoutId => ({
...app,
description: null,
});