Compare commits

..
Author SHA1 Message Date
Meier Lukas 30a336f951 fix: missing updated imports 2025-12-20 22:50:50 +01:00
Meier Lukas 978121b6ed fix(db): remove bulk drizzle-orm export 2025-12-20 22:37:09 +01:00
Manuel fbb5298cb0 feat: enable dns caching by default (#4706) 2025-12-20 21:07:57 +01:00
homarr-renovate[bot] 2dc8feae7e fix(deps): update dependency typescript-eslint to ^8.50.0 (#4720)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 17:25:20 +00:00
homarr-renovate[bot] 6e39524f9d fix(deps): update dependency zod to ^4.2.0 (#4712)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 13:37:00 +00:00
homarr-renovate[bot] b699f28177 chore(deps): update dependency vite@&gt;&#x3d;5.0.0 &lt;&#x3d;5.4.18 to >=7.3.0 (#4713)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 13:35:32 +00:00
homarr-renovate[bot] de39c7e85b chore(deps): update pnpm to v10.26.0 (#4715)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 12:48:32 +00:00
homarr-renovate[bot] e9c33cdd5a chore(deps): update dependency rollup@&gt;&#x3d;4.0.0 &lt;4.22.4 to >=4.53.4 (#4714)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 12:47:56 +00:00
homarr-renovate[bot] 648f7e2c77 fix(deps): update dependency ldapts to v8.0.24 (#4711)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 07:28:01 +00:00
homarr-renovate[bot] 41f40149b3 chore(deps): update dependency hono@&lt;4.6.5 to >=4.11.1 (#4708)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 07:27:41 +00:00
homarr-renovate[bot] 689523a194 fix(deps): update dependency @t3-oss/env-nextjs to ^0.13.10 (#4710)
Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com>
2025-12-20 05:28:31 +00:00
104 changed files with 809 additions and 508 deletions

No files matched your search

+1
View File
@@ -45,6 +45,7 @@ COPY --from=builder /app/apps/nextjs/package.json .
COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs COPY --from=builder /app/apps/tasks/tasks.cjs ./apps/tasks/tasks.cjs
COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs COPY --from=builder /app/apps/websocket/wssServer.cjs ./apps/websocket/wssServer.cjs
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
COPY --from=builder /app/packages/db/migrations ./db/migrations COPY --from=builder /app/packages/db/migrations ./db/migrations
+2 -1
View File
@@ -71,6 +71,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1",
"flag-icons": "^7.5.0", "flag-icons": "^7.5.0",
"glob": "^13.0.0", "glob": "^13.0.0",
"isomorphic-dompurify": "^2.34.0", "isomorphic-dompurify": "^2.34.0",
@@ -87,7 +88,7 @@
"superjson": "2.2.6", "superjson": "2.2.6",
"swagger-ui-react": "^5.31.0", "swagger-ui-react": "^5.31.0",
"use-deep-compare-effect": "^1.8.1", "use-deep-compare-effect": "^1.8.1",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
@@ -1,9 +1,10 @@
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Card, Center, Stack, Text, Title } from "@mantine/core"; import { Card, Center, Stack, Text, Title } from "@mantine/core";
import { and, eq } from "drizzle-orm";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import { isProviderEnabled } from "@homarr/auth/server"; import { isProviderEnabled } from "@homarr/auth/server";
import { and, db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { invites } from "@homarr/db/schema"; import { invites } from "@homarr/db/schema";
import { getScopedI18n } from "@homarr/translation/server"; import { getScopedI18n } from "@homarr/translation/server";
@@ -1,8 +1,9 @@
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { inArray } from "drizzle-orm";
import { auth } from "@homarr/auth/next"; import { auth } from "@homarr/auth/next";
import { isProviderEnabled } from "@homarr/auth/server"; import { isProviderEnabled } from "@homarr/auth/server";
import { db, inArray } from "@homarr/db"; import { db } from "@homarr/db";
import { groups } from "@homarr/db/schema"; import { groups } from "@homarr/db/schema";
import { everyoneGroup } from "@homarr/definitions"; import { everyoneGroup } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server"; import { getScopedI18n } from "@homarr/translation/server";
+2 -1
View File
@@ -1,5 +1,6 @@
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { userAgent } from "next/server"; import { userAgent } from "next/server";
import { eq } from "drizzle-orm";
import { createOpenApiFetchHandler } from "trpc-to-openapi"; import { createOpenApiFetchHandler } from "trpc-to-openapi";
import { appRouter, createTRPCContext } from "@homarr/api"; import { appRouter, createTRPCContext } from "@homarr/api";
@@ -8,7 +9,7 @@ import { hashPasswordAsync } from "@homarr/auth";
import { createSessionAsync } from "@homarr/auth/server"; import { createSessionAsync } from "@homarr/auth/server";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { apiKeys } from "@homarr/db/schema"; import { apiKeys } from "@homarr/db/schema";
const logger = createLogger({ module: "trpcOpenApiRoute" }); const logger = createLogger({ module: "trpcOpenApiRoute" });
@@ -1,9 +1,10 @@
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { eq } from "drizzle-orm";
import DOMPurify from "isomorphic-dompurify"; import DOMPurify from "isomorphic-dompurify";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { medias } from "@homarr/db/schema"; import { medias } from "@homarr/db/schema";
export async function GET(_req: NextRequest, props: { params: Promise<{ id: string }> }) { export async function GET(_req: NextRequest, props: { params: Promise<{ id: string }> }) {
+1
View File
@@ -39,6 +39,7 @@
"@homarr/widgets": "workspace:^0.1.0", "@homarr/widgets": "workspace:^0.1.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1",
"fastify": "^5.6.2", "fastify": "^5.6.2",
"superjson": "2.2.6", "superjson": "2.2.6",
"undici": "7.16.0" "undici": "7.16.0"
+3 -2
View File
@@ -1,10 +1,11 @@
import type { InferInsertModel } from "drizzle-orm";
import { eq } from "drizzle-orm";
import { schedule, validate as validateCron } from "node-cron"; import { schedule, validate as validateCron } from "node-cron";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import type { IJobManager } from "@homarr/cron-job-api"; import type { IJobManager } from "@homarr/cron-job-api";
import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs"; import type { jobGroup as cronJobGroup, JobGroupKeys } from "@homarr/cron-jobs";
import type { Database, InferInsertModel } from "@homarr/db"; import type { Database } from "@homarr/db";
import { eq } from "@homarr/db";
import { cronJobConfigurations } from "@homarr/db/schema"; import { cronJobConfigurations } from "@homarr/db/schema";
const logger = createLogger({ module: "jobManager" }); const logger = createLogger({ module: "jobManager" });
+3 -1
View File
@@ -1,6 +1,8 @@
import { eq, inArray } from "drizzle-orm";
import { env } from "@homarr/auth/env"; import { env } from "@homarr/auth/env";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { db, eq, inArray } from "@homarr/db"; import { db } from "@homarr/db";
import { sessions, users } from "@homarr/db/schema"; import { sessions, users } from "@homarr/db/schema";
import { supportedAuthProviders } from "@homarr/definitions"; import { supportedAuthProviders } from "@homarr/definitions";
+2 -2
View File
@@ -4,10 +4,10 @@
"dependencies": { "dependencies": {
"better-sqlite3": "^12.5.0" "better-sqlite3": "^12.5.0"
}, },
"packageManager": "pnpm@10.25.0", "packageManager": "pnpm@10.26.0",
"engines": { "engines": {
"node": ">=24.12.0", "node": ">=24.12.0",
"pnpm": ">=10.25.0" "pnpm": ">=10.26.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
+6 -5
View File
@@ -59,15 +59,16 @@
"vite-tsconfig-paths": "^5.1.4", "vite-tsconfig-paths": "^5.1.4",
"vitest": "^4.0.15" "vitest": "^4.0.15"
}, },
"packageManager": "pnpm@10.25.0", "packageManager": "pnpm@10.26.0",
"engines": { "engines": {
"node": ">=24.12.0", "node": ">=24.12.0",
"pnpm": ">=10.25.0" "pnpm": ">=10.26.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@tree-sitter-grammars/tree-sitter-yaml", "@tree-sitter-grammars/tree-sitter-yaml",
"bcrypt", "bcrypt",
"better-sqlite3",
"cpu-features", "cpu-features",
"esbuild", "esbuild",
"sharp", "sharp",
@@ -83,18 +84,18 @@
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1", "brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
"esbuild@<=0.24.2": ">=0.27.1", "esbuild@<=0.24.2": ">=0.27.1",
"form-data@>=4.0.0 <4.0.4": ">=4.0.5", "form-data@>=4.0.0 <4.0.4": ">=4.0.5",
"hono@<4.6.5": ">=4.11.0", "hono@<4.6.5": ">=4.11.1",
"linkifyjs@<4.3.2": ">=4.3.2", "linkifyjs@<4.3.2": ">=4.3.2",
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6", "nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
"prismjs@<1.30.0": ">=1.30.0", "prismjs@<1.30.0": ">=1.30.0",
"proxmox-api>undici": "7.16.0", "proxmox-api>undici": "7.16.0",
"react-is": "^19.2.3", "react-is": "^19.2.3",
"rollup@>=4.0.0 <4.22.4": ">=4.53.3", "rollup@>=4.0.0 <4.22.4": ">=4.53.4",
"sha.js@<=2.4.11": ">=2.4.12", "sha.js@<=2.4.11": ">=2.4.12",
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1", "tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1", "tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
"tmp@<=0.2.3": ">=0.2.5", "tmp@<=0.2.3": ">=0.2.5",
"vite@>=5.0.0 <=5.4.18": ">=7.2.7" "vite@>=5.0.0 <=5.4.18": ">=7.3.0"
}, },
"patchedDependencies": { "patchedDependencies": {
"@types/node-unifi": "patches/@types__node-unifi.patch", "@types/node-unifi": "patches/@types__node-unifi.patch",
+1
View File
@@ -26,6 +26,7 @@
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@umami/node": "^0.4.0", "@umami/node": "^0.4.0",
"drizzle-orm": "^0.45.1",
"superjson": "2.2.6" "superjson": "2.2.6"
}, },
"devDependencies": { "devDependencies": {
@@ -1,8 +1,9 @@
import type { UmamiEventData } from "@umami/node"; import type { UmamiEventData } from "@umami/node";
import { Umami } from "@umami/node"; import { Umami } from "@umami/node";
import { count } from "drizzle-orm";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { count, db } from "@homarr/db"; import { db } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries"; import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import { integrations, items, users } from "@homarr/db/schema"; import { integrations, items, users } from "@homarr/db/schema";
import type { defaultServerSettings } from "@homarr/server-settings"; import type { defaultServerSettings } from "@homarr/server-settings";
+2 -1
View File
@@ -46,13 +46,14 @@
"@trpc/react-query": "^11.8.0", "@trpc/react-query": "^11.8.0",
"@trpc/server": "^11.8.0", "@trpc/server": "^11.8.0",
"@trpc/tanstack-react-query": "^11.8.0", "@trpc/tanstack-react-query": "^11.8.0",
"drizzle-orm": "^0.45.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"next": "16.0.10", "next": "16.0.10",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"superjson": "2.2.6", "superjson": "2.2.6",
"trpc-to-openapi": "^3.1.0", "trpc-to-openapi": "^3.1.0",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -1,4 +1,5 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, eq, inArray } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
@@ -7,7 +8,6 @@ import { constructIntegrationPermissions } from "@homarr/auth/shared";
import { decryptSecret } from "@homarr/common/server"; import { decryptSecret } from "@homarr/common/server";
import type { AtLeastOneOf } from "@homarr/common/types"; import type { AtLeastOneOf } from "@homarr/common/types";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq, inArray } from "@homarr/db";
import { integrations } from "@homarr/db/schema"; import { integrations } from "@homarr/db/schema";
import type { IntegrationKind } from "@homarr/definitions"; import type { IntegrationKind } from "@homarr/definitions";
+1 -1
View File
@@ -1,7 +1,7 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { and, eq } from "@homarr/db";
import { items } from "@homarr/db/schema"; import { items } from "@homarr/db/schema";
import type { WidgetKind } from "@homarr/definitions"; import type { WidgetKind } from "@homarr/definitions";
+2 -1
View File
@@ -1,9 +1,10 @@
import { eq } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { generateSecureRandomToken } from "@homarr/common/server"; import { generateSecureRandomToken } from "@homarr/common/server";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { apiKeys } from "@homarr/db/schema"; import { apiKeys } from "@homarr/db/schema";
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";
+4 -6
View File
@@ -1,11 +1,11 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { asc, eq, inArray, like } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { asc, eq, inArray, like } from "@homarr/db";
import { apps } from "@homarr/db/schema"; import { apps } from "@homarr/db/schema";
import { selectAppSchema } from "@homarr/db/validationSchemas"; import { selectAppSchema } from "@homarr/db/validationSchemas";
import { getIconForNameAsync } from "@homarr/icons"; import { getIconForName } from "@homarr/icons";
import { appCreateManySchema, appEditSchema, appManageSchema } from "@homarr/validation/app"; import { appCreateManySchema, appEditSchema, appManageSchema } from "@homarr/validation/app";
import { byIdSchema, paginatedSchema } from "@homarr/validation/common"; import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
@@ -141,15 +141,13 @@ export const appRouter = createTRPCRouter({
.output(z.void()) .output(z.void())
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
await ctx.db.insert(apps).values( await ctx.db.insert(apps).values(
await Promise.all( input.map((app) => ({
input.map(async (app) => ({
id: createId(), id: createId(),
name: app.name, name: app.name,
description: app.description, description: app.description,
iconUrl: app.iconUrl ?? (await getIconForNameAsync(ctx.db, app.name))?.url ?? defaultIcon, iconUrl: app.iconUrl ?? getIconForName(ctx.db, app.name).sync()?.url ?? defaultIcon,
href: app.href, href: app.href,
})), })),
),
); );
}), }),
update: permissionRequiredProcedure update: permissionRequiredProcedure
@@ -1,7 +1,8 @@
import { eq, or } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import { db, eq, or } from "@homarr/db"; import { db } from "@homarr/db";
import { items } from "@homarr/db/schema"; import { items } from "@homarr/db/schema";
import type { WidgetComponentProps } from "../../../../widgets/src"; import type { WidgetComponentProps } from "../../../../widgets/src";
+291 -2
View File
@@ -1,12 +1,14 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import type { InferInsertModel, InferSelectModel, SQL } from "drizzle-orm";
import { and, asc, eq, inArray, isNull, like, not, or, sql } from "drizzle-orm";
import superjson from "superjson"; import superjson from "superjson";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { constructBoardPermissions } from "@homarr/auth/shared"; import { constructBoardPermissions } from "@homarr/auth/shared";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { DeviceType } from "@homarr/common/server"; import type { DeviceType } from "@homarr/common/server";
import type { Database, InferInsertModel, InferSelectModel, SQL } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, asc, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db"; import { handleTransactionsAsync } from "@homarr/db";
import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection"; import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection";
import { getServerSettingByKeyAsync } from "@homarr/db/queries"; import { getServerSettingByKeyAsync } from "@homarr/db/queries";
import { import {
@@ -477,6 +479,47 @@ export const boardRouter = createTRPCRouter({
} }
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
transaction
.insert(boards)
.values({
...boardProps,
id: newBoardId,
name: input.name,
creatorId: ctx.session.user.id,
})
.run();
if (layoutsToInsert.length > 0) {
transaction.insert(layouts).values(layoutsToInsert).run();
}
if (sectionsToInsert.length > 0) {
transaction.insert(sections).values(sectionsToInsert).run();
}
if (sectionLayoutsToInsert.length > 0) {
transaction.insert(sectionLayouts).values(sectionLayoutsToInsert).run();
}
if (sectionCollapseStatesToInsert.length > 0) {
transaction.insert(sectionCollapseStates).values(sectionCollapseStatesToInsert).run();
}
if (itemsToInsert.length > 0) {
transaction.insert(items).values(itemsToInsert).run();
}
if (itemLayoutsToInsert.length > 0) {
transaction.insert(itemLayouts).values(itemLayoutsToInsert).run();
}
if (itemIntegrationsToInsert.length > 0) {
transaction.insert(integrationItems).values(itemIntegrationsToInsert).run();
}
});
},
}); });
}), }),
renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => { renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => {
@@ -913,6 +956,216 @@ export const boardRouter = createTRPCRouter({
} }
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
const addedSections = filterAddedItems(input.sections, dbBoard.sections);
if (addedSections.length > 0) {
transaction
.insert(sections)
.values(
addedSections.map((section) => ({
id: section.id,
kind: section.kind,
yOffset: section.kind !== "dynamic" ? section.yOffset : null,
xOffset: section.kind === "dynamic" ? null : 0,
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
name: "name" in section ? section.name : null,
boardId: dbBoard.id,
})),
)
.run();
if (addedSections.some((section) => section.kind === "dynamic")) {
transaction
.insert(sectionLayouts)
.values(
addedSections
.filter((section) => section.kind === "dynamic")
.flatMap((section) =>
section.layouts.map(
(sectionLayout): InferInsertModel<typeof sectionLayouts> => ({
layoutId: sectionLayout.layoutId,
sectionId: section.id,
parentSectionId: sectionLayout.parentSectionId,
height: sectionLayout.height,
width: sectionLayout.width,
xOffset: sectionLayout.xOffset,
yOffset: sectionLayout.yOffset,
}),
),
),
)
.run();
}
}
const addedItems = filterAddedItems(input.items, dbBoard.items);
if (addedItems.length > 0) {
transaction
.insert(items)
.values(
addedItems.map((item) => ({
id: item.id,
kind: item.kind,
options: superjson.stringify(item.options),
advancedOptions: superjson.stringify(item.advancedOptions),
boardId: dbBoard.id,
})),
)
.run();
transaction
.insert(itemLayouts)
.values(
addedItems.flatMap((item) =>
item.layouts.map(
(layoutSection): InferInsertModel<typeof itemLayouts> => ({
layoutId: layoutSection.layoutId,
sectionId: layoutSection.sectionId,
itemId: item.id,
height: layoutSection.height,
width: layoutSection.width,
xOffset: layoutSection.xOffset,
yOffset: layoutSection.yOffset,
}),
),
),
)
.run();
}
const inputIntegrationRelations = input.items.flatMap(({ integrationIds, id: itemId }) =>
integrationIds.map((integrationId) => ({
integrationId,
itemId,
})),
);
const dbIntegrationRelations = dbBoard.items.flatMap(({ integrationIds, id: itemId }) =>
integrationIds.map((integrationId) => ({
integrationId,
itemId,
})),
);
const addedIntegrationRelations = inputIntegrationRelations.filter(
(inputRelation) =>
!dbIntegrationRelations.some(
(dbRelation) =>
dbRelation.itemId === inputRelation.itemId &&
dbRelation.integrationId === inputRelation.integrationId,
),
);
if (addedIntegrationRelations.length > 0) {
transaction
.insert(integrationItems)
.values(
addedIntegrationRelations.map((relation) => ({
itemId: relation.itemId,
integrationId: relation.integrationId,
})),
)
.run();
}
const updatedItems = filterUpdatedItems(input.items, dbBoard.items);
for (const item of updatedItems) {
transaction
.update(items)
.set({
kind: item.kind,
options: superjson.stringify(item.options),
advancedOptions: superjson.stringify(item.advancedOptions),
})
.where(eq(items.id, item.id))
.run();
for (const itemSectionLayout of item.layouts) {
transaction
.update(itemLayouts)
.set({
height: itemSectionLayout.height,
width: itemSectionLayout.width,
xOffset: itemSectionLayout.xOffset,
yOffset: itemSectionLayout.yOffset,
sectionId: itemSectionLayout.sectionId,
})
.where(and(eq(itemLayouts.itemId, item.id), eq(itemLayouts.layoutId, itemSectionLayout.layoutId)))
.run();
}
}
const updatedSections = filterUpdatedItems(input.sections, dbBoard.sections);
for (const section of updatedSections) {
const prev = dbBoard.sections.find((dbSection) => dbSection.id === section.id);
transaction
.update(sections)
.set({
yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null,
xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null,
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
name: prev?.kind === "category" && "name" in section ? section.name : null,
})
.where(eq(sections.id, section.id))
.run();
if (section.kind !== "dynamic") continue;
for (const sectionLayout of section.layouts) {
transaction
.update(sectionLayouts)
.set({
height: sectionLayout.height,
width: sectionLayout.width,
xOffset: sectionLayout.xOffset,
yOffset: sectionLayout.yOffset,
parentSectionId: sectionLayout.parentSectionId,
})
.where(
and(eq(sectionLayouts.sectionId, section.id), eq(sectionLayouts.layoutId, sectionLayout.layoutId)),
)
.run();
}
}
const removedIntegrationRelations = dbIntegrationRelations.filter(
(dbRelation) =>
!inputIntegrationRelations.some(
(inputRelation) =>
dbRelation.itemId === inputRelation.itemId &&
dbRelation.integrationId === inputRelation.integrationId,
),
);
for (const relation of removedIntegrationRelations) {
transaction
.delete(integrationItems)
.where(
and(
eq(integrationItems.itemId, relation.itemId),
eq(integrationItems.integrationId, relation.integrationId),
),
)
.run();
}
const removedItems = filterRemovedItems(input.items, dbBoard.items);
const itemIds = removedItems.map((item) => item.id);
if (itemIds.length > 0) {
transaction.delete(items).where(inArray(items.id, itemIds)).run();
}
const removedSections = filterRemovedItems(input.sections, dbBoard.sections);
const sectionIds = removedSections.map((section) => section.id);
if (sectionIds.length > 0) {
transaction.delete(sections).where(inArray(sections.id, sectionIds)).run();
}
});
},
}); });
}), }),
getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => { getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
@@ -1005,6 +1258,24 @@ export const boardRouter = createTRPCRouter({
); );
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(boardUserPermissions)
.values(
input.permissions.map((permission) => ({
userId: permission.principalId,
permission: permission.permission,
boardId: input.entityId,
})),
)
.run();
});
},
}); });
}), }),
saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => { saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
@@ -1028,6 +1299,24 @@ export const boardRouter = createTRPCRouter({
); );
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(boardGroupPermissions)
.values(
input.permissions.map((permission) => ({
groupId: permission.principalId,
permission: permission.permission,
boardId: input.entityId,
})),
)
.run();
});
},
}); });
}), }),
importOldmarrConfig: permissionRequiredProcedure importOldmarrConfig: permissionRequiredProcedure
@@ -1,9 +1,10 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import type { SQL } from "drizzle-orm";
import { eq, inArray } from "drizzle-orm";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import { constructBoardPermissions } from "@homarr/auth/shared"; import { constructBoardPermissions } from "@homarr/auth/shared";
import type { Database, SQL } from "@homarr/db"; import type { Database } from "@homarr/db";
import { eq, inArray } from "@homarr/db";
import { boardGroupPermissions, boardUserPermissions, groupMembers } from "@homarr/db/schema"; import { boardGroupPermissions, boardUserPermissions, groupMembers } from "@homarr/db/schema";
import type { BoardPermission } from "@homarr/definitions"; import type { BoardPermission } from "@homarr/definitions";
@@ -1,5 +1,6 @@
import { X509Certificate } from "node:crypto"; import { X509Certificate } from "node:crypto";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { zfd } from "zod-form-data"; import { zfd } from "zod-form-data";
import { z } from "zod/v4"; import { z } from "zod/v4";
@@ -8,7 +9,6 @@ import {
removeCustomRootCertificateAsync, removeCustomRootCertificateAsync,
} from "@homarr/core/infrastructure/certificates"; } from "@homarr/core/infrastructure/certificates";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { and, eq } from "@homarr/db";
import { trustedCertificateHostnames } from "@homarr/db/schema"; import { trustedCertificateHostnames } from "@homarr/db/schema";
import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates"; import { certificateValidFileNameSchema, checkCertificateFile } from "@homarr/validation/certificates";
@@ -91,7 +91,7 @@ export const certificateRouter = createTRPCRouter({
hostname: input.hostname, hostname: input.hostname,
thumbprint: input.thumbprint, thumbprint: input.thumbprint,
}); });
await ctx.db const dbResult = await ctx.db
.delete(trustedCertificateHostnames) .delete(trustedCertificateHostnames)
.where( .where(
and( and(
@@ -103,6 +103,7 @@ export const certificateRouter = createTRPCRouter({
logger.info("Removed trusted hostname", { logger.info("Removed trusted hostname", {
hostname: input.hostname, hostname: input.hostname,
thumbprint: input.thumbprint, thumbprint: input.thumbprint,
count: dbResult.changes,
}); });
}), }),
removeCertificate: permissionRequiredProcedure removeCertificate: permissionRequiredProcedure
+9 -1
View File
@@ -1,9 +1,10 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, eq, like, not } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq, handleTransactionsAsync, like, not } from "@homarr/db"; import { handleTransactionsAsync } from "@homarr/db";
import { getMaxGroupPositionAsync } from "@homarr/db/queries"; import { getMaxGroupPositionAsync } from "@homarr/db/queries";
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema"; import { groupMembers, groupPermissions, groups } from "@homarr/db/schema";
import { everyoneGroup } from "@homarr/definitions"; import { everyoneGroup } from "@homarr/definitions";
@@ -260,6 +261,13 @@ export const groupRouter = createTRPCRouter({
} }
}); });
}, },
handleSync: (db) => {
db.transaction((trx) => {
for (const { id, position } of positions) {
trx.update(groups).set({ position }).where(eq(groups.id, id)).run();
}
});
},
}); });
}), }),
savePermissions: permissionRequiredProcedure savePermissions: permissionRequiredProcedure
+3 -1
View File
@@ -1,5 +1,7 @@
import { eq, inArray, or } from "drizzle-orm";
import { isProviderEnabled } from "@homarr/auth/server"; import { isProviderEnabled } from "@homarr/auth/server";
import { db, eq, inArray, or } from "@homarr/db"; import { db } from "@homarr/db";
import { import {
apps, apps,
boards, boards,
+2 -1
View File
@@ -1,4 +1,5 @@
import { and, like } from "@homarr/db"; import { and, like } from "drizzle-orm";
import { icons } from "@homarr/db/schema"; import { icons } from "@homarr/db/schema";
import { iconsFindSchema } from "@homarr/validation/icons"; import { iconsFindSchema } from "@homarr/validation/icons";
@@ -1,9 +1,10 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import type { SQL } from "drizzle-orm";
import { eq, inArray } from "drizzle-orm";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import { constructIntegrationPermissions } from "@homarr/auth/shared"; import { constructIntegrationPermissions } from "@homarr/auth/shared";
import type { Database, SQL } from "@homarr/db"; import type { Database } from "@homarr/db";
import { eq, inArray } from "@homarr/db";
import { groupMembers, integrationGroupPermissions, integrationUserPermissions } from "@homarr/db/schema"; import { groupMembers, integrationGroupPermissions, integrationUserPermissions } from "@homarr/db/schema";
import type { IntegrationPermission } from "@homarr/definitions"; import type { IntegrationPermission } from "@homarr/definitions";
@@ -1,11 +1,12 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, asc, eq, inArray, like, or } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId, objectEntries } from "@homarr/common"; import { createId, objectEntries } from "@homarr/common";
import { decryptSecret, encryptSecret } from "@homarr/common/server"; import { decryptSecret, encryptSecret } from "@homarr/common/server";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { handleTransactionsAsync } from "@homarr/db";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, asc, eq, handleTransactionsAsync, inArray, like, or } from "@homarr/db";
import { import {
apps, apps,
groupMembers, groupMembers,
@@ -541,6 +542,27 @@ export const integrationRouter = createTRPCRouter({
); );
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
transaction
.delete(integrationUserPermissions)
.where(eq(integrationUserPermissions.integrationId, input.entityId))
.run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(integrationUserPermissions)
.values(
input.permissions.map((permission) => ({
userId: permission.principalId,
permission: permission.permission,
integrationId: input.entityId,
})),
)
.run();
});
},
}); });
}), }),
saveGroupIntegrationPermissions: protectedProcedure saveGroupIntegrationPermissions: protectedProcedure
@@ -566,6 +588,27 @@ export const integrationRouter = createTRPCRouter({
); );
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
transaction
.delete(integrationGroupPermissions)
.where(eq(integrationGroupPermissions.integrationId, input.entityId))
.run();
if (input.permissions.length === 0) {
return;
}
transaction
.insert(integrationGroupPermissions)
.values(
input.permissions.map((permission) => ({
groupId: permission.principalId,
permission: permission.permission,
integrationId: input.entityId,
})),
)
.run();
});
},
}); });
}), }),
searchInIntegration: protectedProcedure searchInIntegration: protectedProcedure
+1 -1
View File
@@ -1,9 +1,9 @@
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { asc, eq } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { asc, eq } from "@homarr/db";
import { invites } from "@homarr/db/schema"; import { invites } from "@homarr/db/schema";
import { selectInviteSchema } from "@homarr/db/validationSchemas"; import { selectInviteSchema } from "@homarr/db/validationSchemas";
@@ -1,9 +1,9 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import type { InferInsertModel } from "drizzle-orm";
import { and, desc, eq, like } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { InferInsertModel } from "@homarr/db";
import { and, desc, eq, like } from "@homarr/db";
import { iconRepositories, icons, medias } from "@homarr/db/schema"; import { iconRepositories, icons, medias } from "@homarr/db/schema";
import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local"; import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local";
import { byIdSchema, paginatedSchema } from "@homarr/validation/common"; import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
@@ -1,8 +1,9 @@
import { eq } from "drizzle-orm";
import { isProviderEnabled } from "@homarr/auth/server"; import { isProviderEnabled } from "@homarr/auth/server";
import { objectEntries } from "@homarr/common"; import { objectEntries } from "@homarr/common";
import type { MaybePromise } from "@homarr/common/types"; import type { MaybePromise } from "@homarr/common/types";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { eq } from "@homarr/db";
import { groups, onboarding } from "@homarr/db/schema"; import { groups, onboarding } from "@homarr/db/schema";
import type { OnboardingStep } from "@homarr/definitions"; import type { OnboardingStep } from "@homarr/definitions";
import { credentialsAdminGroup } from "@homarr/definitions"; import { credentialsAdminGroup } from "@homarr/definitions";
@@ -1,9 +1,9 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { asc, eq, like } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { asc, eq, like } from "@homarr/db";
import { getServerSettingByKeyAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries"; import { getServerSettingByKeyAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
import { searchEngines, users } from "@homarr/db/schema"; import { searchEngines, users } from "@homarr/db/schema";
import { createIntegrationAsync } from "@homarr/integrations"; import { createIntegrationAsync } from "@homarr/integrations";
@@ -1,7 +1,7 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, eq } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { and, eq } from "@homarr/db";
import { sectionCollapseStates, sections } from "@homarr/db/schema"; import { sectionCollapseStates, sections } from "@homarr/db/schema";
import { createTRPCRouter, protectedProcedure } from "../../trpc"; import { createTRPCRouter, protectedProcedure } from "../../trpc";
+3 -2
View File
@@ -1,10 +1,11 @@
import type { InferInsertModel } from "drizzle-orm";
import { and, eq, not } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { describe, expect, it, test, vi } from "vitest"; import { describe, expect, it, test, vi } from "vitest";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { Database, InferInsertModel } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq, not } from "@homarr/db";
import { import {
boardGroupPermissions, boardGroupPermissions,
boards, boards,
@@ -1,8 +1,8 @@
import { eq } from "drizzle-orm";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import * as authShared from "@homarr/auth/shared"; import * as authShared from "@homarr/auth/shared";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { eq } from "@homarr/db";
import { boards, users } from "@homarr/db/schema"; import { boards, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
+1 -1
View File
@@ -1,9 +1,9 @@
import { eq } from "drizzle-orm";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import * as env from "@homarr/auth/env"; import * as env from "@homarr/auth/env";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { eq } from "@homarr/db";
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema"; import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
import type { GroupPermissionKey } from "@homarr/definitions"; import type { GroupPermissionKey } from "@homarr/definitions";
@@ -1,8 +1,8 @@
import { eq } from "drizzle-orm";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import * as authShared from "@homarr/auth/shared"; import * as authShared from "@homarr/auth/shared";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { eq } from "@homarr/db";
import { integrations, users } from "@homarr/db/schema"; import { integrations, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
+1 -1
View File
@@ -1,9 +1,9 @@
import { eq } from "drizzle-orm";
import { describe, expect, it, test, vi } from "vitest"; import { describe, expect, it, test, vi } from "vitest";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { eq } from "@homarr/db";
import { invites, onboarding, users } from "@homarr/db/schema"; import { invites, onboarding, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
import type { GroupPermissionKey, OnboardingStep } from "@homarr/definitions"; import type { GroupPermissionKey, OnboardingStep } from "@homarr/definitions";
+1 -1
View File
@@ -1,11 +1,11 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { and, eq, like } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq, like } from "@homarr/db";
import { getMaxGroupPositionAsync } from "@homarr/db/queries"; import { getMaxGroupPositionAsync } from "@homarr/db/queries";
import { boards, groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema"; import { boards, groupMembers, groupPermissions, groups, invites, users } from "@homarr/db/schema";
import { selectUserSchema } from "@homarr/db/validationSchemas"; import { selectUserSchema } from "@homarr/db/validationSchemas";
@@ -1,9 +1,9 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import type { Modify } from "@homarr/common/types"; import type { Modify } from "@homarr/common/types";
import { eq } from "@homarr/db";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { users } from "@homarr/db/schema"; import { users } from "@homarr/db/schema";
import { userChangeSearchPreferencesSchema } from "@homarr/validation/user"; import { userChangeSearchPreferencesSchema } from "@homarr/validation/user";
+1 -1
View File
@@ -1,8 +1,8 @@
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { eq } from "@homarr/db";
import { boards, items } from "@homarr/db/schema"; import { boards, items } from "@homarr/db/schema";
import { createTRPCRouter, protectedProcedure } from "../../trpc"; import { createTRPCRouter, protectedProcedure } from "../../trpc";
+1 -1
View File
@@ -1,8 +1,8 @@
import type { Adapter } from "@auth/core/adapters"; import type { Adapter } from "@auth/core/adapters";
import { DrizzleAdapter } from "@auth/drizzle-adapter"; import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { and, eq } from "drizzle-orm";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq } from "@homarr/db";
import { accounts, sessions, users } from "@homarr/db/schema"; import { accounts, sessions, users } from "@homarr/db/schema";
import type { SupportedAuthProvider } from "@homarr/definitions"; import type { SupportedAuthProvider } from "@homarr/definitions";
+1 -1
View File
@@ -1,9 +1,9 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { eq, inArray } from "drizzle-orm";
import type { NextAuthConfig } from "next-auth"; import type { NextAuthConfig } from "next-auth";
import type { Session } from "@homarr/auth"; import type { Session } from "@homarr/auth";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { eq, inArray } from "@homarr/db";
import { groupMembers, groupPermissions, users } from "@homarr/db/schema"; import { groupMembers, groupPermissions, users } from "@homarr/db/schema";
import { getPermissionsWithChildren } from "@homarr/definitions"; import { getPermissionsWithChildren } from "@homarr/definitions";
+1 -1
View File
@@ -1,9 +1,9 @@
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { and, eq, inArray } from "drizzle-orm";
import type { NextAuthConfig } from "next-auth"; import type { NextAuthConfig } from "next-auth";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { and, eq, inArray } from "@homarr/db";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { groupMembers, groups, users } from "@homarr/db/schema"; import { groupMembers, groups, users } from "@homarr/db/schema";
import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions"; import { colorSchemeCookieKey, everyoneGroup } from "@homarr/definitions";
+3 -2
View File
@@ -32,12 +32,13 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
"cookies": "^0.9.1", "cookies": "^0.9.1",
"ldapts": "8.0.23", "drizzle-orm": "^0.45.1",
"ldapts": "8.0.24",
"next": "16.0.10", "next": "16.0.10",
"next-auth": "5.0.0-beta.30", "next-auth": "5.0.0-beta.30",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
@@ -1,7 +1,7 @@
import { and, eq, inArray, or } from "drizzle-orm";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq, inArray, or } from "@homarr/db";
import { boards, boardUserPermissions, groupMembers } from "@homarr/db/schema"; import { boards, boardUserPermissions, groupMembers } from "@homarr/db/schema";
import type { IntegrationPermission } from "@homarr/definitions"; import type { IntegrationPermission } from "@homarr/definitions";
@@ -1,6 +1,7 @@
import { eq, inArray } from "drizzle-orm";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import { db, eq, inArray } from "@homarr/db"; import { db } from "@homarr/db";
import { groupMembers, integrationGroupPermissions, integrationUserPermissions } from "@homarr/db/schema"; import { groupMembers, integrationGroupPermissions, integrationUserPermissions } from "@homarr/db/schema";
import { constructIntegrationPermissions } from "./integration-permissions"; import { constructIntegrationPermissions } from "./integration-permissions";
@@ -1,8 +1,8 @@
import type { InferInsertModel } from "drizzle-orm";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { InferInsertModel } from "@homarr/db";
import { boardGroupPermissions, boards, boardUserPermissions, groupMembers, groups, users } from "@homarr/db/schema"; import { boardGroupPermissions, boards, boardUserPermissions, groupMembers, groups, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
@@ -1,9 +1,9 @@
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { and, eq } from "drizzle-orm";
import type { z } from "zod/v4"; import type { z } from "zod/v4";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq } from "@homarr/db";
import { users } from "@homarr/db/schema"; import { users } from "@homarr/db/schema";
import type { userSignInSchema } from "@homarr/validation/user"; import type { userSignInSchema } from "@homarr/validation/user";
@@ -1,10 +1,11 @@
import { CredentialsSignin } from "@auth/core/errors"; import { CredentialsSignin } from "@auth/core/errors";
import { and, eq } from "drizzle-orm";
import type { InferInsertModel } from "drizzle-orm";
import { z } from "zod/v4"; import { z } from "zod/v4";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import type { Database, InferInsertModel } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq } from "@homarr/db";
import { users } from "@homarr/db/schema"; import { users } from "@homarr/db/schema";
import type { ldapSignInSchema } from "@homarr/validation/user"; import type { ldapSignInSchema } from "@homarr/validation/user";
@@ -1,9 +1,9 @@
import { CredentialsSignin } from "@auth/core/errors"; import { CredentialsSignin } from "@auth/core/errors";
import { and, eq } from "drizzle-orm";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { and, eq } from "@homarr/db";
import { groups, users } from "@homarr/db/schema"; import { groups, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
+1 -1
View File
@@ -1,9 +1,9 @@
import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies"; import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies";
import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import { eq } from "drizzle-orm";
import { describe, expect, test, vi } from "vitest"; import { describe, expect, test, vi } from "vitest";
import { eq } from "@homarr/db";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { groupMembers, groups, users } from "@homarr/db/schema"; import { groupMembers, groups, users } from "@homarr/db/schema";
import { createDb } from "@homarr/db/test"; import { createDb } from "@homarr/db/test";
+2 -1
View File
@@ -28,7 +28,8 @@
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"dotenv": "^17.2.3" "dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -1
View File
@@ -1,6 +1,7 @@
import { command } from "@drizzle-team/brocli"; import { command } from "@drizzle-team/brocli";
import { eq } from "drizzle-orm";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { users } from "@homarr/db/schema"; import { users } from "@homarr/db/schema";
export const fixUsernames = command({ export const fixUsernames = command({
+2 -1
View File
@@ -1,9 +1,10 @@
import { command, string } from "@drizzle-team/brocli"; import { command, string } from "@drizzle-team/brocli";
import { and, count, eq } from "drizzle-orm";
import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; import { createSaltAsync, hashPasswordAsync } from "@homarr/auth";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { generateSecureRandomToken } from "@homarr/common/server"; import { generateSecureRandomToken } from "@homarr/common/server";
import { and, count, db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { getMaxGroupPositionAsync } from "@homarr/db/queries"; import { getMaxGroupPositionAsync } from "@homarr/db/queries";
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema"; import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
import { usernameSchema } from "@homarr/validation/user"; import { usernameSchema } from "@homarr/validation/user";
+2 -1
View File
@@ -1,8 +1,9 @@
import { command, string } from "@drizzle-team/brocli"; import { command, string } from "@drizzle-team/brocli";
import { and, eq } from "drizzle-orm";
import { hashPasswordAsync } from "@homarr/auth"; import { hashPasswordAsync } from "@homarr/auth";
import { generateSecureRandomToken } from "@homarr/common/server"; import { generateSecureRandomToken } from "@homarr/common/server";
import { and, db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { sessions, users } from "@homarr/db/schema"; import { sessions, users } from "@homarr/db/schema";
export const resetPassword = command({ export const resetPassword = command({
+1 -1
View File
@@ -34,7 +34,7 @@
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"undici": "7.16.0", "undici": "7.16.0",
"zod": "^4.1.13", "zod": "^4.2.0",
"zod-validation-error": "^5.0.0" "zod-validation-error": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
+4 -3
View File
@@ -37,8 +37,8 @@
}, },
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@libsql/client": "^0.15.15", "@t3-oss/env-nextjs": "^0.13.10",
"@t3-oss/env-nextjs": "^0.13.8", "better-sqlite3": "^12.5.0",
"dns-caching": "^0.2.9", "dns-caching": "^0.2.9",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"ioredis": "5.8.2", "ioredis": "5.8.2",
@@ -46,12 +46,13 @@
"pg": "^8.16.3", "pg": "^8.16.3",
"superjson": "2.2.6", "superjson": "2.2.6",
"winston": "3.19.0", "winston": "3.19.0",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0", "@types/pg": "^8.16.0",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"typescript": "^5.9.3" "typescript": "^5.9.3"
@@ -1,10 +1,10 @@
import { createClient } from "@libsql/client"; import Database from "better-sqlite3";
import { drizzle as drizzleSqlite } from "drizzle-orm/libsql"; import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
import { dbEnv } from "../env"; import { dbEnv } from "../env";
import type { SharedDrizzleConfig } from "./shared"; import type { SharedDrizzleConfig } from "./shared";
export const createSqliteDb = <TSchema extends Record<string, unknown>>(config: SharedDrizzleConfig<TSchema>) => { export const createSqliteDb = <TSchema extends Record<string, unknown>>(config: SharedDrizzleConfig<TSchema>) => {
const client = createClient({ url: `file:${dbEnv.URL}` }); const connection = new Database(dbEnv.URL);
return drizzleSqlite<TSchema>(client, config); return drizzleSqlite<TSchema>(connection, config);
}; };
+1 -1
View File
@@ -2,7 +2,7 @@ import { createBooleanSchema, createEnv } from "../env";
export const dnsEnv = createEnv({ export const dnsEnv = createEnv({
server: { server: {
ENABLE_DNS_CACHING: createBooleanSchema(false), ENABLE_DNS_CACHING: createBooleanSchema(true),
}, },
experimental__runtimeEnv: process.env, experimental__runtimeEnv: process.env,
}); });
+1 -1
View File
@@ -34,7 +34,7 @@
"@trpc/tanstack-react-query": "^11.8.0", "@trpc/tanstack-react-query": "^11.8.0",
"node-cron": "^4.2.1", "node-cron": "^4.2.1",
"react": "19.2.3", "react": "19.2.3",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1
View File
@@ -38,6 +38,7 @@
"@homarr/server-settings": "workspace:^0.1.0", "@homarr/server-settings": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"drizzle-orm": "^0.45.1",
"semver-parser": "^4.1.8" "semver-parser": "^4.1.8"
}, },
"devDependencies": { "devDependencies": {
+2 -1
View File
@@ -1,9 +1,10 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions"; import { EVERY_MINUTE } from "@homarr/cron-jobs-core/expressions";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { items } from "@homarr/db/schema"; import { items } from "@homarr/db/schema";
import { dockerContainersRequestHandler } from "@homarr/request-handler/docker"; import { dockerContainersRequestHandler } from "@homarr/request-handler/docker";
+44 -2
View File
@@ -1,9 +1,11 @@
import type { InferInsertModel } from "drizzle-orm";
import { inArray, sql } from "drizzle-orm";
import { createId, splitToNChunks, Stopwatch } from "@homarr/common"; import { createId, splitToNChunks, Stopwatch } from "@homarr/common";
import { env } from "@homarr/common/env"; import { env } from "@homarr/common/env";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions"; import { EVERY_WEEK } from "@homarr/cron-jobs-core/expressions";
import type { InferInsertModel } from "@homarr/db"; import { db, handleTransactionsAsync } from "@homarr/db";
import { db, handleTransactionsAsync, inArray, sql } from "@homarr/db";
import { iconRepositories, icons } from "@homarr/db/schema"; import { iconRepositories, icons } from "@homarr/db/schema";
import { fetchIconsAsync } from "@homarr/icons"; import { fetchIconsAsync } from "@homarr/icons";
@@ -121,6 +123,46 @@ export const iconsUpdaterJob = createCronJob("iconsUpdater", EVERY_WEEK, {
); );
} }
countDeleted += deadIcons.length;
});
},
handleSync() {
db.transaction((transaction) => {
if (newIconRepositories.length >= 1) {
transaction.insert(iconRepositories).values(newIconRepositories).run();
}
if (newIcons.length >= 1) {
// We only insert 5000 icons at a time to avoid SQLite limitations
for (const chunck of splitToNChunks(newIcons, Math.ceil(newIcons.length / 5000))) {
transaction.insert(icons).values(chunck).run();
}
}
if (deadIcons.length >= 1) {
transaction
.delete(icons)
.where(
inArray(
// Combine iconRepositoryId and checksum to allow same icons on different repositories
sql`concat(${icons.iconRepositoryId}, '.', ${icons.checksum})`,
deadIcons.map((icon) => `${icon.iconRepositoryId}.${icon.checksum}`),
),
)
.run();
}
if (deadIconRepositories.length >= 1) {
transaction
.delete(iconRepositories)
.where(
inArray(
iconRepositories.id,
deadIconRepositories.map((iconRepository) => iconRepository.id),
),
)
.run();
}
countDeleted += deadIcons.length; countDeleted += deadIcons.length;
}); });
}, },
@@ -1,7 +1,8 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions"; import { EVERY_5_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { items } from "@homarr/db/schema"; import { items } from "@homarr/db/schema";
import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status"; import { minecraftServerStatusRequestHandler } from "@homarr/request-handler/minecraft-server-status";
+2 -1
View File
@@ -1,9 +1,10 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { items } from "@homarr/db/schema"; import { items } from "@homarr/db/schema";
// This import is done that way to avoid circular dependencies. // This import is done that way to avoid circular dependencies.
import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds"; import { rssFeedsRequestHandler } from "@homarr/request-handler/rss-feeds";
+2 -1
View File
@@ -1,9 +1,10 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions"; import { EVERY_10_MINUTES } from "@homarr/cron-jobs-core/expressions";
import { db, eq } from "@homarr/db"; import { db } from "@homarr/db";
import { items } from "@homarr/db/schema"; import { items } from "@homarr/db/schema";
import { weatherRequestHandler } from "@homarr/request-handler/weather"; import { weatherRequestHandler } from "@homarr/request-handler/weather";
+15 -3
View File
@@ -31,8 +31,20 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
return { return {
...context, ...context,
insertAll: (db: HomarrDatabase) => {
db.transaction((transaction) => {
for (const [key, values] of objectEntries(context)) {
if (values.length >= 1) {
transaction
.insert(schema[key])
.values(values as never)
.run();
}
}
});
},
// We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors // We allow any database that supports async passed here but then fallback to mysql to prevent typescript errors
insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql | HomarrDatabase) => { insertAllAsync: async (db: HomarrDatabaseMysql | HomarrDatabasePostgresql) => {
const innerDb = db as HomarrDatabaseMysql; const innerDb = db as HomarrDatabaseMysql;
await innerDb.transaction(async (transaction) => { await innerDb.transaction(async (transaction) => {
for (const [key, values] of objectEntries(context)) { for (const [key, values] of objectEntries(context)) {
@@ -49,7 +61,7 @@ export const createDbInsertCollectionForTransaction = <TTableKey extends TableKe
export const createDbInsertCollectionWithoutTransaction = <TTableKey extends TableKey>( export const createDbInsertCollectionWithoutTransaction = <TTableKey extends TableKey>(
tablesInInsertOrder: TTableKey[], tablesInInsertOrder: TTableKey[],
) => { ) => {
const { insertAllAsync, ...collection } = createDbInsertCollectionForTransaction(tablesInInsertOrder); const { insertAll, insertAllAsync, ...collection } = createDbInsertCollectionForTransaction(tablesInInsertOrder);
return { return {
...collection, ...collection,
@@ -63,7 +75,7 @@ export const createDbInsertCollectionWithoutTransaction = <TTableKey extends Tab
default: default:
// For better-sqlite3, we need to use the synchronous insertAll method // For better-sqlite3, we need to use the synchronous insertAll method
// default assumes better-sqlite3. It's original implementation. // default assumes better-sqlite3. It's original implementation.
await insertAllAsync(db); insertAll(db);
break; break;
} }
}, },
+2 -2
View File
@@ -1,4 +1,4 @@
import type { LibSQLDatabase } from "drizzle-orm/libsql"; import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
import type { MySql2Database } from "drizzle-orm/mysql2"; import type { MySql2Database } from "drizzle-orm/mysql2";
import type { NodePgDatabase } from "drizzle-orm/node-postgres"; import type { NodePgDatabase } from "drizzle-orm/node-postgres";
@@ -6,6 +6,6 @@ import type * as mysqlSchema from "./schema/mysql";
import type * as pgSchema from "./schema/postgresql"; import type * as pgSchema from "./schema/postgresql";
import type * as sqliteSchema from "./schema/sqlite"; import type * as sqliteSchema from "./schema/sqlite";
export type HomarrDatabase = LibSQLDatabase<typeof sqliteSchema>; export type HomarrDatabase = BetterSQLite3Database<typeof sqliteSchema>;
export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>; export type HomarrDatabaseMysql = MySql2Database<typeof mysqlSchema>;
export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>; export type HomarrDatabasePostgresql = NodePgDatabase<typeof pgSchema>;
-1
View File
@@ -2,7 +2,6 @@ import { createDb } from "@homarr/core/infrastructure/db";
import { schema } from "./schema"; import { schema } from "./schema";
export * from "drizzle-orm";
export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver"; export type { HomarrDatabaseMysql, HomarrDatabasePostgresql } from "./driver";
export const db = createDb(schema); export const db = createDb(schema);
@@ -1,10 +1,10 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { IntegrationKind } from "@homarr/definitions"; import type { IntegrationKind } from "@homarr/definitions";
import { getIntegrationKindsByCategory } from "@homarr/definitions"; import { getIntegrationKindsByCategory } from "@homarr/definitions";
import { eq } from "../..";
import type { Database } from "../.."; import type { Database } from "../..";
import { items } from "../../schema"; import { items } from "../../schema";
@@ -1,5 +1,6 @@
import { and, eq } from "drizzle-orm";
import type { Database } from "../.."; import type { Database } from "../..";
import { and, eq } from "../..";
import { integrationSecrets } from "../../schema"; import { integrationSecrets } from "../../schema";
/** /**
@@ -1,6 +1,6 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { eq } from "../..";
import type { Database } from "../.."; import type { Database } from "../..";
import { items } from "../../schema"; import { items } from "../../schema";
+2 -1
View File
@@ -1,3 +1,5 @@
import { eq } from "drizzle-orm";
import { createId, objectKeys } from "@homarr/common"; import { createId, objectKeys } from "@homarr/common";
import { import {
createDocumentationLink, createDocumentationLink,
@@ -9,7 +11,6 @@ import {
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings"; import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
import type { Database } from ".."; import type { Database } from "..";
import { eq } from "..";
import { import {
getServerSettingByKeyAsync, getServerSettingByKeyAsync,
insertServerSettingByKeyAsync, insertServerSettingByKeyAsync,
+2
View File
@@ -51,6 +51,7 @@
"@paralleldrive/cuid2": "^3.1.0", "@paralleldrive/cuid2": "^3.1.0",
"@testcontainers/mysql": "^11.10.0", "@testcontainers/mysql": "^11.10.0",
"@testcontainers/postgresql": "^11.10.0", "@testcontainers/postgresql": "^11.10.0",
"better-sqlite3": "^12.5.0",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-kit": "^0.31.8", "drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
@@ -63,6 +64,7 @@
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
"@homarr/prettier-config": "workspace:^0.1.0", "@homarr/prettier-config": "workspace:^0.1.0",
"@homarr/tsconfig": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "^8.16.0", "@types/pg": "^8.16.0",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"esbuild": "^0.27.1", "esbuild": "^0.27.1",
+2 -1
View File
@@ -1,7 +1,8 @@
import { inArray } from "drizzle-orm";
import type { WidgetKind } from "@homarr/definitions"; import type { WidgetKind } from "@homarr/definitions";
import type { Database } from ".."; import type { Database } from "..";
import { inArray } from "..";
import type { inferSupportedIntegrations } from "../../widgets/src"; import type { inferSupportedIntegrations } from "../../widgets/src";
import { items } from "../schema"; import { items } from "../schema";
+1 -1
View File
@@ -1,10 +1,10 @@
import { eq } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import type { ServerSettings } from "@homarr/server-settings"; import type { ServerSettings } from "@homarr/server-settings";
import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings"; import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings";
import type { Database } from ".."; import type { Database } from "..";
import { eq } from "..";
import { serverSettings } from "../schema"; import { serverSettings } from "../schema";
export const getServerSettingsAsync = async (db: Database) => { export const getServerSettingsAsync = async (db: Database) => {
+5 -6
View File
@@ -1,16 +1,15 @@
import { createClient } from "@libsql/client"; import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/libsql"; import { drizzle } from "drizzle-orm/better-sqlite3";
import { migrate } from "drizzle-orm/libsql/migrator"; import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; import { DB_CASING } from "@homarr/core/infrastructure/db/constants";
import * as sqliteSchema from "../schema/sqlite"; import * as sqliteSchema from "../schema/sqlite";
export const createDb = (debug?: boolean) => { export const createDb = (debug?: boolean) => {
const sqlite = createClient({ url: ":memory:" }); const sqlite = new Database(":memory:");
const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING }); const db = drizzle(sqlite, { schema: sqliteSchema, logger: debug, casing: DB_CASING });
// TODO: Make async and awaitable migrate(db, {
void migrate(db, {
migrationsFolder: "./packages/db/migrations/sqlite", migrationsFolder: "./packages/db/migrations/sqlite",
}); });
+7
View File
@@ -1,9 +1,11 @@
import { isMysql, isPostgresql } from "./collection";
import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver"; import type { HomarrDatabase, HomarrDatabaseMysql } from "./driver";
import type { MySqlSchema } from "./schema"; import type { MySqlSchema } from "./schema";
import * as schema from "./schema"; import * as schema from "./schema";
interface HandleTransactionInput { interface HandleTransactionInput {
handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise<void>; handleAsync: (db: HomarrDatabaseMysql, schema: MySqlSchema) => Promise<void>;
handleSync: (db: HomarrDatabase) => void;
} }
/** /**
@@ -12,5 +14,10 @@ interface HandleTransactionInput {
* But it can also generally be used when dealing with different database drivers. * But it can also generally be used when dealing with different database drivers.
*/ */
export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => { export const handleDiffrentDbDriverOperationsAsync = async (db: HomarrDatabase, input: HandleTransactionInput) => {
if (isMysql() || isPostgresql()) {
// Schema type is always the correct one based on env variables
await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema); await input.handleAsync(db as unknown as HomarrDatabaseMysql, schema as unknown as MySqlSchema);
} else {
input.handleSync(db);
}
}; };
+1 -1
View File
@@ -25,7 +25,7 @@
"dependencies": { "dependencies": {
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"fast-xml-parser": "^5.3.3", "fast-xml-parser": "^5.3.3",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -28,7 +28,7 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/form": "^8.3.10", "@mantine/form": "^8.3.10",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -32,7 +32,7 @@
"@homarr/validation": "workspace:^0.1.0", "@homarr/validation": "workspace:^0.1.0",
"@mantine/core": "^8.3.10", "@mantine/core": "^8.3.10",
"react": "19.2.3", "react": "19.2.3",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -1
View File
@@ -25,7 +25,8 @@
"dependencies": { "dependencies": {
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"@homarr/core": "workspace:^0.1.0", "@homarr/core": "workspace:^0.1.0",
"@homarr/db": "workspace:^0.1.0" "@homarr/db": "workspace:^0.1.0",
"drizzle-orm": "^0.45.1"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+4 -3
View File
@@ -1,9 +1,10 @@
import { like } from "drizzle-orm";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { like } from "@homarr/db";
import { icons } from "@homarr/db/schema"; import { icons } from "@homarr/db/schema";
export const getIconForNameAsync = async (db: Database, name: string) => { export const getIconForName = (db: Database, name: string) => {
return await db.query.icons.findFirst({ return db.query.icons.findFirst({
where: like(icons.name, `%${name}%`), where: like(icons.name, `%${name}%`),
}); });
}; };
@@ -1,6 +1,6 @@
import { createHash } from "crypto"; import { createHash } from "crypto";
import type { InferSelectModel } from "drizzle-orm";
import type { InferSelectModel } from "@homarr/db";
import { db } from "@homarr/db"; import { db } from "@homarr/db";
import type { medias } from "@homarr/db/schema"; import type { medias } from "@homarr/db/schema";
+1 -1
View File
@@ -48,7 +48,7 @@
"tsdav": "^2.1.6", "tsdav": "^2.1.6",
"undici": "7.16.0", "undici": "7.16.0",
"xml2js": "^0.6.2", "xml2js": "^0.6.2",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+1 -1
View File
@@ -39,7 +39,7 @@
"next": "16.0.10", "next": "16.0.10",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -1
View File
@@ -40,11 +40,12 @@
"@mantine/core": "^8.3.10", "@mantine/core": "^8.3.10",
"@mantine/hooks": "^8.3.10", "@mantine/hooks": "^8.3.10",
"adm-zip": "0.5.16", "adm-zip": "0.5.16",
"drizzle-orm": "^0.45.1",
"next": "16.0.10", "next": "16.0.10",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3", "react-dom": "19.2.3",
"superjson": "2.2.6", "superjson": "2.2.6",
"zod": "^4.1.13", "zod": "^4.2.0",
"zod-form-data": "^3.0.1" "zod-form-data": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
@@ -45,6 +45,13 @@ export const importInitialOldmarrAsync = async (
await integrationInsertCollection.insertAllAsync(transaction); await integrationInsertCollection.insertAllAsync(transaction);
}); });
}, },
handleSync(db) {
db.transaction((transaction) => {
boardInsertCollection.insertAll(transaction);
userInsertCollection.insertAll(transaction);
integrationInsertCollection.insertAll(transaction);
});
},
}); });
logger.info("Import successful", { duration: stopwatch.getElapsedInHumanWords() }); logger.info("Import successful", { duration: stopwatch.getElapsedInHumanWords() });
@@ -1,4 +1,6 @@
import { handleTransactionsAsync, inArray } from "@homarr/db"; import { inArray } from "drizzle-orm";
import { handleTransactionsAsync } from "@homarr/db";
import type { Database } from "@homarr/db"; import type { Database } from "@homarr/db";
import { apps } from "@homarr/db/schema"; import { apps } from "@homarr/db/schema";
import type { OldmarrConfig } from "@homarr/old-schema"; import type { OldmarrConfig } from "@homarr/old-schema";
@@ -35,5 +37,8 @@ export const importSingleOldmarrConfigAsync = async (
async handleAsync(db) { async handleAsync(db) {
await boardInsertCollection.insertAllAsync(db); await boardInsertCollection.insertAllAsync(db);
}, },
handleSync(db) {
boardInsertCollection.insertAll(db);
},
}); });
}; };
+2 -1
View File
@@ -1,4 +1,5 @@
import type { InferSelectModel } from "@homarr/db"; import type { InferSelectModel } from "drizzle-orm";
import type { apps } from "@homarr/db/schema"; import type { apps } from "@homarr/db/schema";
import type { OldmarrApp } from "@homarr/old-schema"; import type { OldmarrApp } from "@homarr/old-schema";
+2 -1
View File
@@ -1,5 +1,6 @@
import type { InferInsertModel } from "drizzle-orm";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { InferInsertModel } from "@homarr/db";
import type { boards } from "@homarr/db/schema"; import type { boards } from "@homarr/db/schema";
import type { prepareMultipleImports } from "../prepare/prepare-multiple"; import type { prepareMultipleImports } from "../prepare/prepare-multiple";
+1 -1
View File
@@ -1,8 +1,8 @@
import type { InferInsertModel } from "drizzle-orm";
import SuperJSON from "superjson"; import SuperJSON from "superjson";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { createLogger } from "@homarr/core/infrastructure/logs"; import { createLogger } from "@homarr/core/infrastructure/logs";
import type { InferInsertModel } from "@homarr/db";
import type { itemLayouts, items } from "@homarr/db/schema"; import type { itemLayouts, items } from "@homarr/db/schema";
import type { BoardSize, OldmarrApp, OldmarrWidget } from "@homarr/old-schema"; import type { BoardSize, OldmarrApp, OldmarrWidget } from "@homarr/old-schema";
import { boardSizes } from "@homarr/old-schema"; import { boardSizes } from "@homarr/old-schema";
@@ -1,5 +1,6 @@
import type { InferInsertModel } from "drizzle-orm";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import type { InferInsertModel } from "@homarr/db";
import type { sections } from "@homarr/db/schema"; import type { sections } from "@homarr/db/schema";
import type { OldmarrCategorySection, OldmarrEmptySection } from "@homarr/old-schema"; import type { OldmarrCategorySection, OldmarrEmptySection } from "@homarr/old-schema";
+2 -1
View File
@@ -1,6 +1,7 @@
import type { InferInsertModel } from "drizzle-orm";
import { createId } from "@homarr/common"; import { createId } from "@homarr/common";
import { decryptSecretWithKey } from "@homarr/common/server"; import { decryptSecretWithKey } from "@homarr/common/server";
import type { InferInsertModel } from "@homarr/db";
import type { users } from "@homarr/db/schema"; import type { users } from "@homarr/db/schema";
import type { OldmarrImportUser } from "../user-schema"; import type { OldmarrImportUser } from "../user-schema";
@@ -1,4 +1,5 @@
import type { InferSelectModel } from "@homarr/db"; import type { InferSelectModel } from "drizzle-orm";
import type { apps } from "@homarr/db/schema"; import type { apps } from "@homarr/db/schema";
import type { OldmarrConfig } from "@homarr/old-schema"; import type { OldmarrConfig } from "@homarr/old-schema";
+1 -1
View File
@@ -23,7 +23,7 @@
"prettier": "@homarr/prettier-config", "prettier": "@homarr/prettier-config",
"dependencies": { "dependencies": {
"@homarr/common": "workspace:^0.1.0", "@homarr/common": "workspace:^0.1.0",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -1
View File
@@ -31,10 +31,11 @@
"@homarr/integrations": "workspace:^0.1.0", "@homarr/integrations": "workspace:^0.1.0",
"@homarr/redis": "workspace:^0.1.0", "@homarr/redis": "workspace:^0.1.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"drizzle-orm": "^0.45.1",
"octokit": "^5.0.5", "octokit": "^5.0.5",
"superjson": "2.2.6", "superjson": "2.2.6",
"undici": "7.16.0", "undici": "7.16.0",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
+2 -1
View File
@@ -1,7 +1,8 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import type { ContainerInfo, ContainerStats } from "dockerode"; import type { ContainerInfo, ContainerStats } from "dockerode";
import { like, or } from "drizzle-orm";
import { db, like, or } from "@homarr/db"; import { db } from "@homarr/db";
import { icons } from "@homarr/db/schema"; import { icons } from "@homarr/db/schema";
import type { ContainerState } from "@homarr/docker"; import type { ContainerState } from "@homarr/docker";
import { dockerLabels, DockerSingleton } from "@homarr/docker"; import { dockerLabels, DockerSingleton } from "@homarr/docker";
+1 -1
View File
@@ -24,7 +24,7 @@
"dependencies": { "dependencies": {
"@homarr/definitions": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0",
"@homarr/translation": "workspace:^0.1.0", "@homarr/translation": "workspace:^0.1.0",
"zod": "^4.1.13", "zod": "^4.2.0",
"zod-form-data": "^3.0.1" "zod-form-data": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
+2 -1
View File
@@ -70,6 +70,7 @@
"@tiptap/starter-kit": "^3.13.0", "@tiptap/starter-kit": "^3.13.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"drizzle-orm": "^0.45.1",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "^1.3.0",
"mantine-react-table": "2.0.0-beta.9", "mantine-react-table": "2.0.0-beta.9",
"next": "16.0.10", "next": "16.0.10",
@@ -78,7 +79,7 @@
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"recharts": "^2.15.4", "recharts": "^2.15.4",
"video.js": "^8.23.4", "video.js": "^8.23.4",
"zod": "^4.1.13" "zod": "^4.2.0"
}, },
"devDependencies": { "devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0", "@homarr/eslint-config": "workspace:^0.2.0",
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff. Show more