diff --git a/Dockerfile b/Dockerfile index fda2e1dc7..7ae43e72f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.17.0-alpine AS base +FROM node:20.18.0-alpine AS base FROM base AS builder RUN apk add --no-cache libc6-compat diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 9a8c7ec6c..5f410d270 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -42,12 +42,12 @@ "@mantine/hooks": "^7.13.2", "@mantine/modals": "^7.13.2", "@mantine/tiptap": "^7.13.2", - "@million/lint": "1.0.8", + "@million/lint": "1.0.9", "@t3-oss/env-nextjs": "^0.11.1", "@tabler/icons-react": "^3.19.0", - "@tanstack/react-query": "^5.59.0", - "@tanstack/react-query-devtools": "^5.59.0", - "@tanstack/react-query-next-experimental": "5.59.0", + "@tanstack/react-query": "^5.59.9", + "@tanstack/react-query-devtools": "^5.59.9", + "@tanstack/react-query-next-experimental": "5.59.9", "@trpc/client": "next", "@trpc/next": "next", "@trpc/react-query": "next", @@ -63,14 +63,14 @@ "glob": "^11.0.0", "jotai": "^2.10.0", "mantine-react-table": "2.0.0-beta.6", - "next": "^14.2.14", + "next": "^14.2.15", "postcss-preset-mantine": "^1.17.0", "prismjs": "^1.29.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-error-boundary": "^4.0.13", "react-simple-code-editor": "^0.14.1", - "sass": "^1.79.4", + "sass": "^1.79.5", "superjson": "2.2.1", "swagger-ui-react": "^5.17.14", "use-deep-compare-effect": "^1.8.1" @@ -80,15 +80,15 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/chroma-js": "2.4.4", - "@types/node": "^20.16.10", + "@types/node": "^20.16.11", "@types/prismjs": "^1.26.4", "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.0", + "@types/react-dom": "^18.3.1", "@types/swagger-ui-react": "^4.18.3", "concurrently": "^9.0.1", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "node-loader": "^2.0.0", "prettier": "^3.3.3", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx b/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx index fd01444d7..e5e867815 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx @@ -3,7 +3,7 @@ import type { PropsWithChildren } from "react"; import { useState } from "react"; import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core"; -import { createTheme, isMantineColorScheme, MantineProvider } from "@mantine/core"; +import { createTheme, DirectionProvider, isMantineColorScheme, MantineProvider } from "@mantine/core"; import dayjs from "dayjs"; import { clientApi } from "@homarr/api/client"; @@ -14,16 +14,18 @@ export const CustomMantineProvider = ({ children }: PropsWithChildren) => { const manager = useColorSchemeManager(); return ( - - {children} - + + + {children} + + ); }; diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx index 0c745c656..f4ce00f67 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx @@ -12,8 +12,7 @@ import type { useForm } from "@homarr/form"; import { useZodForm } from "@homarr/form"; import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; import { useScopedI18n } from "@homarr/translation/client"; -import type { z } from "@homarr/validation"; -import { validation } from "@homarr/validation"; +import { validation, z } from "@homarr/validation"; interface LoginFormProps { providers: string[]; @@ -22,15 +21,17 @@ interface LoginFormProps { callbackUrl: string; } +const extendedValidation = validation.user.signIn.extend({ provider: z.enum(["credentials", "ldap"]) }); + export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => { const t = useScopedI18n("user"); const router = useRouter(); const [isPending, setIsPending] = useState(false); - const form = useZodForm(validation.user.signIn, { + const form = useZodForm(extendedValidation, { initialValues: { name: "", password: "", - credentialType: "basic", + provider: "credentials", }, }); @@ -95,14 +96,14 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c {credentialInputsVisible && ( <> -
void signInAsync("credentials", credentials))}> + void signInAsync(credentials.provider, credentials))}> {providers.includes("credentials") && ( - + {t("action.login.label")} @@ -110,7 +111,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c )} {providers.includes("ldap") && ( - + {t("action.login.labelWith", { provider: "LDAP" })} )} @@ -133,18 +134,18 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c interface SubmitButtonProps { isPending: boolean; form: ReturnType FormType>>; - credentialType: "basic" | "ldap"; + provider: "credentials" | "ldap"; } -const SubmitButton = ({ isPending, form, credentialType, children }: PropsWithChildren) => { - const isCurrentProviderActive = form.getValues().credentialType === credentialType; +const SubmitButton = ({ isPending, form, provider, children }: PropsWithChildren) => { + const isCurrentProviderActive = form.getValues().provider === provider; return ( - - - - - - - - - - - - + {canCreateIntegrations && ( + <> + + + + + + + + + + + + + + + + + + + )} @@ -102,6 +110,8 @@ interface IntegrationListProps { const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps) => { const t = await getScopedI18n("integration"); + const session = await auth(); + const hasFullAccess = session?.user.permissions.includes("integration-full-all") ?? false; if (integrations.length === 0) { return
{t("page.list.empty")}
; @@ -151,18 +161,21 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps - - - - - - + {hasFullAccess || + (integration.permissions.hasFullAccess && ( + + + + + + + ))} @@ -177,18 +190,21 @@ const IntegrationList = async ({ integrations, activeTab }: IntegrationListProps {integration.name} - - - - - - + {hasFullAccess || + (integration.permissions.hasFullAccess && ( + + + + + + + ))} {integration.url} diff --git a/apps/nextjs/src/app/[locale]/manage/layout.tsx b/apps/nextjs/src/app/[locale]/manage/layout.tsx index b4d1564c4..e0bd9fcb5 100644 --- a/apps/nextjs/src/app/[locale]/manage/layout.tsx +++ b/apps/nextjs/src/app/[locale]/manage/layout.tsx @@ -23,6 +23,7 @@ import { IconUsersGroup, } from "@tabler/icons-react"; +import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; import { getScopedI18n } from "@homarr/translation/server"; @@ -33,6 +34,7 @@ import { ClientShell } from "~/components/layout/shell"; export default async function ManageLayout({ children }: PropsWithChildren) { const t = await getScopedI18n("management.navbar"); + const session = await auth(); const navigationLinks: NavigationLink[] = [ { label: t("items.home"), @@ -62,6 +64,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) { { icon: IconUser, label: t("items.users.label"), + hidden: !session?.user.permissions.includes("admin"), items: [ { label: t("items.users.items.manage"), @@ -84,6 +87,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) { { label: t("items.tools.label"), icon: IconTool, + hidden: !session?.user.permissions.includes("admin"), items: [ { label: t("items.tools.items.docker"), @@ -111,6 +115,7 @@ export default async function ManageLayout({ children }: PropsWithChildren) { label: t("items.settings"), href: "/manage/settings", icon: IconSettings, + hidden: !session?.user.permissions.includes("admin"), }, { label: t("items.help.label"), diff --git a/apps/nextjs/src/app/[locale]/manage/page.tsx b/apps/nextjs/src/app/[locale]/manage/page.tsx index 8e25ffb92..31137d121 100644 --- a/apps/nextjs/src/app/[locale]/manage/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/page.tsx @@ -3,6 +3,7 @@ import { Card, Group, SimpleGrid, Space, Stack, Text } from "@mantine/core"; import { IconArrowRight } from "@tabler/icons-react"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; import { getScopedI18n } from "@homarr/translation/server"; @@ -28,6 +29,7 @@ export async function generateMetadata() { export default async function ManagementPage() { const statistics = await api.home.getStats(); + const session = await auth(); const t = await getScopedI18n("management.page.home"); const links: LinkProps[] = [ @@ -35,38 +37,40 @@ export default async function ManagementPage() { count: statistics.countBoards, href: "/manage/boards", subtitle: t("statisticLabel.boards"), - title: t("statistic.countBoards"), + title: t("statistic.board"), }, { count: statistics.countUsers, href: "/manage/users", subtitle: t("statisticLabel.authentication"), - title: t("statistic.createUser"), + title: t("statistic.user"), + hidden: !session?.user.permissions.includes("admin"), }, { - hidden: !isProviderEnabled("credentials"), count: statistics.countInvites, href: "/manage/users/invites", subtitle: t("statisticLabel.authentication"), - title: t("statistic.createInvite"), + title: t("statistic.invite"), + hidden: !isProviderEnabled("credentials") || !session?.user.permissions.includes("admin"), }, { count: statistics.countIntegrations, href: "/manage/integrations", subtitle: t("statisticLabel.resources"), - title: t("statistic.addIntegration"), + title: t("statistic.integration"), }, { count: statistics.countApps, href: "/manage/apps", subtitle: t("statisticLabel.resources"), - title: t("statistic.addApp"), + title: t("statistic.app"), }, { count: statistics.countGroups, href: "/manage/users/groups", subtitle: t("statisticLabel.authorization"), - title: t("statistic.manageRoles"), + title: t("statistic.group"), + hidden: !session?.user.permissions.includes("admin"), }, ]; return ( diff --git a/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx b/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx index 2951e31a9..436380990 100644 --- a/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/search-engines/page.tsx @@ -31,7 +31,6 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) { const searchParams = searchParamsSchema.parse(props.searchParams); const { items: searchEngines, totalCount } = await api.searchEngine.getPaginated(searchParams); - const t = await getI18n(); const tEngine = await getScopedI18n("search.engine"); return ( @@ -40,13 +39,7 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) { {tEngine("page.list.title")} - + {tEngine("page.create.title")} diff --git a/apps/nextjs/src/app/[locale]/manage/tools/api/components/api-keys.tsx b/apps/nextjs/src/app/[locale]/manage/tools/api/components/api-keys.tsx new file mode 100644 index 000000000..86503427c --- /dev/null +++ b/apps/nextjs/src/app/[locale]/manage/tools/api/components/api-keys.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { useMemo } from "react"; +import { Button, Group, Stack, Text, Title } from "@mantine/core"; +import type { MRT_ColumnDef } from "mantine-react-table"; +import { MantineReactTable, useMantineReactTable } from "mantine-react-table"; + +import type { RouterOutputs } from "@homarr/api"; +import { clientApi } from "@homarr/api/client"; +import { revalidatePathActionAsync } from "@homarr/common/client"; +import { useModalAction } from "@homarr/modals"; +import { useScopedI18n } from "@homarr/translation/client"; +import { UserAvatar } from "@homarr/ui"; + +import { CopyApiKeyModal } from "~/app/[locale]/manage/tools/api/components/copy-api-key-modal"; + +interface ApiKeysManagementProps { + apiKeys: RouterOutputs["apiKeys"]["getAll"]; +} + +export const ApiKeysManagement = ({ apiKeys }: ApiKeysManagementProps) => { + const { openModal } = useModalAction(CopyApiKeyModal); + const { mutate, isPending } = clientApi.apiKeys.create.useMutation({ + async onSuccess(data) { + openModal({ + apiKey: data.randomToken, + }); + await revalidatePathActionAsync("/manage/tools/api"); + }, + }); + const t = useScopedI18n("management.page.tool.api.tab.apiKey"); + + const columns = useMemo[]>( + () => [ + { + accessorKey: "id", + header: t("table.header.id"), + }, + { + accessorKey: "user", + header: t("table.header.createdBy"), + Cell: ({ row }) => ( + + + {row.original.user.name} + + ), + }, + ], + [], + ); + + const table = useMantineReactTable({ + columns, + data: apiKeys, + renderTopToolbarCustomActions: () => ( + + ), + enableDensityToggle: false, + state: { + density: "xs", + }, + }); + + return ( + + {t("title")} + + + ); +}; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/api/components/copy-api-key-modal.tsx b/apps/nextjs/src/app/[locale]/manage/tools/api/components/copy-api-key-modal.tsx new file mode 100644 index 000000000..4a3ba621d --- /dev/null +++ b/apps/nextjs/src/app/[locale]/manage/tools/api/components/copy-api-key-modal.tsx @@ -0,0 +1,34 @@ +import { Button, CopyButton, PasswordInput, Stack, Text } from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; + +import { createModal } from "@homarr/modals"; +import { useScopedI18n } from "@homarr/translation/client"; + +export const CopyApiKeyModal = createModal<{ apiKey: string }>(({ actions, innerProps }) => { + const t = useScopedI18n("management.page.tool.api.modal.createApiToken"); + const [visible, { toggle }] = useDisclosure(false); + return ( + + {t("description")} + + + {({ copy }) => ( + + )} + + + ); +}).withOptions({ + defaultTitle(t) { + return t("management.page.tool.api.modal.createApiToken.title"); + }, +}); diff --git a/apps/nextjs/src/app/[locale]/manage/tools/api/components/swagger-ui.tsx b/apps/nextjs/src/app/[locale]/manage/tools/api/components/swagger-ui.tsx new file mode 100644 index 000000000..476a37c5a --- /dev/null +++ b/apps/nextjs/src/app/[locale]/manage/tools/api/components/swagger-ui.tsx @@ -0,0 +1,23 @@ +"use client"; + +import type { OpenAPIV3 } from "openapi-types"; +import SwaggerUI from "swagger-ui-react"; + +// workaround for CSS that cannot be processed by next.js, https://github.com/swagger-api/swagger-ui/issues/10045 +import "../swagger-ui-dark.css"; +import "../swagger-ui-overrides.css"; +import "../swagger-ui.css"; + +interface SwaggerUIClientProps { + document: OpenAPIV3.Document; +} + +export const SwaggerUIClient = ({ document }: SwaggerUIClientProps) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const requestInterceptor = (req: Record) => { + req.credentials = "omit"; + return req; + }; + + return ; +}; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx index 2c2c0f1da..d91fdd3f7 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/api/page.tsx @@ -1,19 +1,23 @@ -import { getScopedI18n } from "@homarr/translation/server"; - -// workaround for CSS that cannot be processed by next.js, https://github.com/swagger-api/swagger-ui/issues/10045 -import "./swagger-ui-dark.css"; -import "./swagger-ui-overrides.css"; -import "./swagger-ui.css"; - import { headers } from "next/headers"; -import SwaggerUI from "swagger-ui-react"; +import { notFound } from "next/navigation"; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from "@mantine/core"; import { openApiDocument } from "@homarr/api"; +import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { extractBaseUrlFromHeaders } from "@homarr/common"; +import { getScopedI18n } from "@homarr/translation/server"; +import { SwaggerUIClient } from "~/app/[locale]/manage/tools/api/components/swagger-ui"; import { createMetaTitle } from "~/metadata"; +import { ApiKeysManagement } from "./components/api-keys"; export async function generateMetadata() { + const session = await auth(); + if (!session?.user || !session.user.permissions.includes("admin")) { + return {}; + } + const t = await getScopedI18n("management"); return { @@ -21,8 +25,29 @@ export async function generateMetadata() { }; } -export default function ApiPage() { +export default async function ApiPage() { + const session = await auth(); + if (!session?.user || !session.user.permissions.includes("admin")) { + notFound(); + } const document = openApiDocument(extractBaseUrlFromHeaders(headers())); - - return ; + const apiKeys = await api.apiKeys.getAll(); + const t = await getScopedI18n("management.page.tool.api.tab"); + + return ( + + + + {t("documentation.label")} + {t("apiKey.label")} + + + + + + + + + + ); } diff --git a/apps/nextjs/src/app/[locale]/manage/tools/api/swagger-ui.css b/apps/nextjs/src/app/[locale]/manage/tools/api/swagger-ui.css index 0385684c1..e02e30307 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/api/swagger-ui.css +++ b/apps/nextjs/src/app/[locale]/manage/tools/api/swagger-ui.css @@ -7146,9 +7146,6 @@ } .swagger-ui .wrapper { box-sizing: border-box; - margin: 0 auto; - max-width: 1460px; - padding: 0 20px; width: 100%; } .swagger-ui .opblock-tag-section { @@ -7734,7 +7731,7 @@ background: #fff; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15); margin: 0 0 20px; - padding: 30px 0; + padding: 30px 20px; } .swagger-ui .scheme-container .schemes { align-items: flex-end; diff --git a/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx index da45b84bb..131ce7859 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx @@ -4,12 +4,20 @@ import { getScopedI18n } from "@homarr/translation/server"; import "@xterm/xterm/css/xterm.css"; +import { notFound } from "next/navigation"; + +import { auth } from "@homarr/auth/next"; + import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; import { fullHeightWithoutHeaderAndFooter } from "~/constants"; import { createMetaTitle } from "~/metadata"; import { ClientSideTerminalComponent } from "./client"; export async function generateMetadata() { + const session = await auth(); + if (!session?.user || !session.user.permissions.includes("admin")) { + return {}; + } const t = await getScopedI18n("management"); return { @@ -17,7 +25,12 @@ export async function generateMetadata() { }; } -export default function LogsManagementPage() { +export default async function LogsManagementPage() { + const session = await auth(); + if (!session?.user || !session.user.permissions.includes("admin")) { + notFound(); + } + return ( <> diff --git a/apps/nextjs/src/app/[locale]/manage/tools/tasks/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/tasks/page.tsx index 834935659..94def6599 100644 --- a/apps/nextjs/src/app/[locale]/manage/tools/tasks/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/tools/tasks/page.tsx @@ -1,12 +1,18 @@ +import { notFound } from "next/navigation"; import { Box, Title } from "@mantine/core"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { getScopedI18n } from "@homarr/translation/server"; import { createMetaTitle } from "~/metadata"; import { JobsList } from "./_components/jobs-list"; export async function generateMetadata() { + const session = await auth(); + if (!session?.user.permissions.includes("admin")) { + return {}; + } const t = await getScopedI18n("management"); return { @@ -15,6 +21,11 @@ export async function generateMetadata() { } export default async function TasksPage() { + const session = await auth(); + if (!session?.user.permissions.includes("admin")) { + notFound(); + } + const jobs = await api.cronJobs.getJobs(); return ( diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx new file mode 100644 index 000000000..4c437db4a --- /dev/null +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/_components/_first-day-of-week.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { Button, Group, Radio, Stack } from "@mantine/core"; +import dayjs from "dayjs"; +import localeData from "dayjs/plugin/localeData"; + +import type { RouterOutputs } from "@homarr/api"; +import { clientApi } from "@homarr/api/client"; +import { revalidatePathActionAsync } from "@homarr/common/client"; +import { useZodForm } from "@homarr/form"; +import { showErrorNotification, showSuccessNotification } from "@homarr/notifications"; +import { useI18n } from "@homarr/translation/client"; +import type { z } from "@homarr/validation"; +import { validation } from "@homarr/validation"; + +dayjs.extend(localeData); + +interface FirstDayOfWeekProps { + user: RouterOutputs["user"]["getById"]; +} + +const weekDays = dayjs.weekdays(false); + +export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => { + const t = useI18n(); + const { mutate, isPending } = clientApi.user.changeFirstDayOfWeek.useMutation({ + async onSettled() { + await revalidatePathActionAsync(`/manage/users/${user.id}`); + }, + onSuccess(_, variables) { + form.setInitialValues({ + firstDayOfWeek: variables.firstDayOfWeek, + }); + showSuccessNotification({ + message: t("user.action.changeFirstDayOfWeek.notification.success.message"), + }); + }, + onError() { + showErrorNotification({ + message: t("user.action.changeFirstDayOfWeek.notification.error.message"), + }); + }, + }); + const form = useZodForm(validation.user.firstDayOfWeek, { + initialValues: { + firstDayOfWeek: user.firstDayOfWeek, + }, + }); + + const handleSubmit = (values: FormType) => { + mutate({ + id: user.id, + ...values, + }); + }; + + const inputProps = form.getInputProps("firstDayOfWeek"); + const onChange = inputProps.onChange as (value: number) => void; + const value = (inputProps.value as number).toString(); + + return ( + + + onChange(parseInt(value))}> + + + + + + + + + + + + + ); +}; + +type FormType = z.infer; diff --git a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx index 7d1ee0302..e29a140f3 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/[userId]/general/page.tsx @@ -13,6 +13,7 @@ import { createMetaTitle } from "~/metadata"; import { canAccessUserEditPage } from "../access"; import { ChangeHomeBoardForm } from "./_components/_change-home-board"; import { DeleteUserButton } from "./_components/_delete-user-button"; +import { FirstDayOfWeek } from "./_components/_first-day-of-week"; import { UserProfileAvatarForm } from "./_components/_profile-avatar-form"; import { UserProfileForm } from "./_components/_profile-form"; @@ -93,6 +94,11 @@ export default async function EditUserPage({ params }: Props) { /> + + {tGeneral("item.firstDayOfWeek")} + + + {isCredentialsUser && ( diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx index adad026ee..972b62d99 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/[id]/members/page.tsx @@ -48,13 +48,7 @@ export default async function GroupsDetailPage({ params, searchParams }: GroupsD )} - + {isProviderEnabled("credentials") && ( member.id)} /> )} diff --git a/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx index da8ce6967..3fce6db2a 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/groups/page.tsx @@ -1,8 +1,10 @@ import Link from "next/link"; +import { notFound } from "next/navigation"; import { Anchor, Group, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from "@mantine/core"; import type { RouterOutputs } from "@homarr/api"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { getI18n } from "@homarr/translation/server"; import { SearchInput, TablePagination, UserAvatarGroup } from "@homarr/ui"; import { z } from "@homarr/validation"; @@ -26,6 +28,12 @@ interface GroupsListPageProps { } export default async function GroupsListPage(props: GroupsListPageProps) { + const session = await auth(); + + if (!session?.user.permissions.includes("admin")) { + return notFound(); + } + const t = await getI18n(); const searchParams = searchParamsSchema.parse(props.searchParams); const { items: groups, totalCount } = await api.group.getPaginated(searchParams); @@ -36,13 +44,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) { {t("group.title")} - + diff --git a/apps/nextjs/src/app/[locale]/manage/users/invites/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/invites/page.tsx index 4044fb5b5..f392e8282 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/invites/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/invites/page.tsx @@ -1,6 +1,7 @@ import { notFound } from "next/navigation"; import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb"; @@ -11,6 +12,11 @@ export default async function InvitesOverviewPage() { notFound(); } + const session = await auth(); + if (!session?.user.permissions.includes("admin")) { + return notFound(); + } + const initialInvites = await api.invite.getAll(); return ( <> diff --git a/apps/nextjs/src/app/[locale]/manage/users/page.tsx b/apps/nextjs/src/app/[locale]/manage/users/page.tsx index f77be0eeb..94ae5c103 100644 --- a/apps/nextjs/src/app/[locale]/manage/users/page.tsx +++ b/apps/nextjs/src/app/[locale]/manage/users/page.tsx @@ -1,4 +1,7 @@ +import { notFound } from "next/navigation"; + import { api } from "@homarr/api/server"; +import { auth } from "@homarr/auth/next"; import { isProviderEnabled } from "@homarr/auth/server"; import { getScopedI18n } from "@homarr/translation/server"; @@ -7,6 +10,10 @@ import { createMetaTitle } from "~/metadata"; import { UserListComponent } from "./_components/user-list"; export async function generateMetadata() { + const session = await auth(); + if (!session?.user.permissions.includes("admin")) { + return {}; + } const t = await getScopedI18n("management.page.user.list"); return { @@ -15,6 +22,11 @@ export async function generateMetadata() { } export default async function UsersPage() { + const session = await auth(); + if (!session?.user.permissions.includes("admin")) { + return notFound(); + } + const userList = await api.user.getAll(); const credentialsProviderEnabled = isProviderEnabled("credentials"); diff --git a/apps/nextjs/src/app/api/[...trpc]/route.ts b/apps/nextjs/src/app/api/[...trpc]/route.ts index e6abf7741..669a2bd0c 100644 --- a/apps/nextjs/src/app/api/[...trpc]/route.ts +++ b/apps/nextjs/src/app/api/[...trpc]/route.ts @@ -1,14 +1,59 @@ import { createOpenApiFetchHandler } from "trpc-swagger/build/index.mjs"; import { appRouter, createTRPCContext } from "@homarr/api"; +import type { Session } from "@homarr/auth"; +import { createSessionAsync } from "@homarr/auth/server"; +import { db, eq } from "@homarr/db"; +import { apiKeys } from "@homarr/db/schema/sqlite"; +import { logger } from "@homarr/log"; + +const handlerAsync = async (req: Request) => { + const apiKeyHeaderValue = req.headers.get("ApiKey"); + const session: Session | null = await getSessionOrDefaultFromHeadersAsync(apiKeyHeaderValue); -const handler = (req: Request) => { return createOpenApiFetchHandler({ req, endpoint: "/", router: appRouter, - createContext: () => createTRPCContext({ session: null, headers: req.headers }), + createContext: () => createTRPCContext({ session, headers: req.headers }), + }); +}; + +const getSessionOrDefaultFromHeadersAsync = async (apiKeyHeaderValue: string | null): Promise => { + logger.info( + `Creating OpenAPI fetch handler for user ${apiKeyHeaderValue ? "with an api key" : "without an api key"}`, + ); + + if (apiKeyHeaderValue === null) { + return null; + } + + const apiKeyFromDb = await db.query.apiKeys.findFirst({ + where: eq(apiKeys.apiKey, apiKeyHeaderValue), + columns: { + id: true, + apiKey: false, + salt: false, + }, + with: { + user: { + columns: { + id: true, + name: true, + email: true, + emailVerified: true, + }, + }, + }, }); + + if (apiKeyFromDb === undefined) { + logger.warn("An attempt to authenticate over API has failed"); + return null; + } + + logger.info(`Read session from API request and found user ${apiKeyFromDb.user.name} (${apiKeyFromDb.user.id})`); + return await createSessionAsync(db, apiKeyFromDb.user); }; -export { handler as GET, handler as POST }; +export { handlerAsync as GET, handlerAsync as POST }; diff --git a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts index 0c17a094e..7aae12a0d 100644 --- a/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts +++ b/apps/nextjs/src/app/api/auth/[...nextauth]/route.ts @@ -1,17 +1,37 @@ import { NextRequest } from "next/server"; import { createHandlers } from "@homarr/auth"; +import type { SupportedAuthProvider } from "@homarr/definitions"; import { logger } from "@homarr/log"; export const GET = async (req: NextRequest) => { - return await createHandlers(isCredentialsRequest(req)).handlers.GET(reqWithTrustedOrigin(req)); + return await createHandlers(extractProvider(req)).handlers.GET(reqWithTrustedOrigin(req)); }; export const POST = async (req: NextRequest) => { - return await createHandlers(isCredentialsRequest(req)).handlers.POST(reqWithTrustedOrigin(req)); + return await createHandlers(extractProvider(req)).handlers.POST(reqWithTrustedOrigin(req)); }; -const isCredentialsRequest = (req: NextRequest) => { - return req.url.includes("credentials") && req.method === "POST"; +/** + * This method extracts the used provider from the url and allows us to override the getUserByEmail method in the adapter. + * @param req request containing the url + * @returns the provider or "unknown" if the provider could not be extracted + */ +const extractProvider = (req: NextRequest): SupportedAuthProvider | "unknown" => { + const url = new URL(req.url); + + if (url.pathname.includes("oidc")) { + return "oidc"; + } + + if (url.pathname.includes("credentials")) { + return "credentials"; + } + + if (url.pathname.includes("ldap")) { + return "ldap"; + } + + return "unknown"; }; /** diff --git a/apps/nextjs/src/components/layout/header/search.tsx b/apps/nextjs/src/components/layout/header/search.tsx index 9b2edd78f..6b5fe883e 100644 --- a/apps/nextjs/src/components/layout/header/search.tsx +++ b/apps/nextjs/src/components/layout/header/search.tsx @@ -21,10 +21,7 @@ export const DesktopSearchInput = () => { leftSection={} onClick={openSpotlight} > - {t("common.rtl", { - value: t("search.placeholder"), - symbol: "...", - })} + {`${t("search.placeholder")}...`} ); }; diff --git a/apps/tasks/package.json b/apps/tasks/package.json index 23c3afa46..ac21b2feb 100644 --- a/apps/tasks/package.json +++ b/apps/tasks/package.json @@ -38,17 +38,17 @@ "dayjs": "^1.11.13", "dotenv": "^16.4.5", "superjson": "2.2.1", - "undici": "6.19.8" + "undici": "6.20.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "@types/node": "^20.16.10", + "@types/node": "^20.16.11", "dotenv-cli": "^7.4.2", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "prettier": "^3.3.3", "tsx": "4.13.3", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/apps/websocket/package.json b/apps/websocket/package.json index 84e5e68ee..10f974e45 100644 --- a/apps/websocket/package.json +++ b/apps/websocket/package.json @@ -34,8 +34,8 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/ws": "^8.5.12", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "prettier": "^3.3.3", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/package.json b/package.json index fe2259f7e..0827b1d81 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,11 @@ "prettier": "^3.3.3", "testcontainers": "^10.13.2", "turbo": "^2.1.3", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "vite-tsconfig-paths": "^5.0.1", "vitest": "^2.1.2" }, - "packageManager": "pnpm@9.12.0", + "packageManager": "pnpm@9.12.1", "engines": { "node": ">=20.18.0" }, diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 366379727..64bb1adc9 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -32,7 +32,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/api/package.json b/packages/api/package.json index c89783da6..be3235d98 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -39,7 +39,7 @@ "@trpc/react-query": "next", "@trpc/server": "next", "dockerode": "^4.0.2", - "next": "^14.2.14", + "next": "^14.2.15", "react": "^18.3.1", "superjson": "2.2.1", "trpc-swagger": "^1.2.6" @@ -49,8 +49,8 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/dockerode": "^3.3.31", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "prettier": "^3.3.3", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/packages/api/src/open-api.ts b/packages/api/src/open-api.ts index 935c682e6..02949c882 100644 --- a/packages/api/src/open-api.ts +++ b/packages/api/src/open-api.ts @@ -8,4 +8,12 @@ export const openApiDocument = (base: string) => version: "1.0.0", baseUrl: base, docsUrl: "https://homarr.dev", + securitySchemes: { + apikey: { + type: "apiKey", + name: "ApiKey", + description: "API key which can be obtained in the Homarr administration dashboard", + in: "header", + }, + }, }); diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts index a54648739..cd528106f 100644 --- a/packages/api/src/root.ts +++ b/packages/api/src/root.ts @@ -1,3 +1,4 @@ +import { apiKeysRouter } from "./router/apiKeys"; import { appRouter as innerAppRouter } from "./router/app"; import { boardRouter } from "./router/board"; import { cronJobsRouter } from "./router/cron-jobs"; @@ -31,6 +32,7 @@ export const appRouter = createTRPCRouter({ docker: dockerRouter, serverSettings: serverSettingsRouter, cronJobs: cronJobsRouter, + apiKeys: apiKeysRouter, }); // export type definition of API diff --git a/packages/api/src/router/apiKeys.ts b/packages/api/src/router/apiKeys.ts new file mode 100644 index 000000000..df5fd85d0 --- /dev/null +++ b/packages/api/src/router/apiKeys.ts @@ -0,0 +1,41 @@ +import { createSaltAsync, hashPasswordAsync } from "@homarr/auth"; +import { generateSecureRandomToken } from "@homarr/common/server"; +import { createId, db } from "@homarr/db"; +import { apiKeys } from "@homarr/db/schema/sqlite"; + +import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; + +export const apiKeysRouter = createTRPCRouter({ + getAll: permissionRequiredProcedure.requiresPermission("admin").query(() => { + return db.query.apiKeys.findMany({ + columns: { + id: true, + apiKey: false, + salt: false, + }, + with: { + user: { + columns: { + id: true, + name: true, + image: true, + }, + }, + }, + }); + }), + create: permissionRequiredProcedure.requiresPermission("admin").mutation(async ({ ctx }) => { + const salt = await createSaltAsync(); + const randomToken = generateSecureRandomToken(64); + const hashedRandomToken = await hashPasswordAsync(randomToken, salt); + await db.insert(apiKeys).values({ + id: createId(), + apiKey: hashedRandomToken, + salt, + userId: ctx.session.user.id, + }); + return { + randomToken, + }; + }), +}); diff --git a/packages/api/src/router/app.ts b/packages/api/src/router/app.ts index b6319625c..1987671fd 100644 --- a/packages/api/src/router/app.ts +++ b/packages/api/src/router/app.ts @@ -4,57 +4,118 @@ import { asc, createId, eq, like } from "@homarr/db"; import { apps } from "@homarr/db/schema/sqlite"; import { validation, z } from "@homarr/validation"; -import { createTRPCRouter, publicProcedure } from "../trpc"; +import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; export const appRouter = createTRPCRouter({ - all: publicProcedure.query(async ({ ctx }) => { - return await ctx.db.query.apps.findMany({ - orderBy: asc(apps.name), - }); - }), - selectable: publicProcedure.query(async ({ ctx }) => { - return await ctx.db.query.apps.findMany({ - columns: { - id: true, - name: true, - iconUrl: true, - }, - orderBy: asc(apps.name), - }); - }), + all: publicProcedure + .input(z.void()) + .output( + z.array( + z.object({ + name: z.string(), + id: z.string(), + description: z.string().nullable(), + iconUrl: z.string(), + href: z.string().nullable(), + }), + ), + ) + .meta({ openapi: { method: "GET", path: "/api/apps", tags: ["apps"], protect: true } }) + .query(({ ctx }) => { + return ctx.db.query.apps.findMany({ + orderBy: asc(apps.name), + }); + }), search: publicProcedure .input(z.object({ query: z.string(), limit: z.number().min(1).max(100).default(10) })) - .query(async ({ ctx, input }) => { - return await ctx.db.query.apps.findMany({ + .output( + z.array( + z.object({ + name: z.string(), + id: z.string(), + description: z.string().nullable(), + iconUrl: z.string(), + href: z.string().nullable(), + }), + ), + ) + .meta({ openapi: { method: "GET", path: "/api/apps/search", tags: ["apps"], protect: true } }) + .query(({ ctx, input }) => { + return ctx.db.query.apps.findMany({ where: like(apps.name, `%${input.query}%`), orderBy: asc(apps.name), limit: input.limit, }); }), - byId: publicProcedure.input(validation.common.byId).query(async ({ ctx, input }) => { - const app = await ctx.db.query.apps.findFirst({ - where: eq(apps.id, input.id), - }); - - if (!app) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "App not found", + selectable: publicProcedure + .input(z.void()) + .output( + z.array( + z.object({ + name: z.string(), + id: z.string(), + iconUrl: z.string(), + }), + ), + ) + .meta({ + openapi: { + method: "GET", + path: "/api/apps/selectable", + tags: ["apps"], + protect: true, + }, + }) + .query(({ ctx }) => { + return ctx.db.query.apps.findMany({ + columns: { + id: true, + name: true, + iconUrl: true, + }, + orderBy: asc(apps.name), + }); + }), + byId: publicProcedure + .input(validation.common.byId) + .output( + z.object({ + name: z.string(), + id: z.string(), + description: z.string().nullable(), + iconUrl: z.string(), + href: z.string().nullable(), + }), + ) + .meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } }) + .query(async ({ ctx, input }) => { + const app = await ctx.db.query.apps.findFirst({ + where: eq(apps.id, input.id), }); - } - return app; - }), - create: publicProcedure.input(validation.app.manage).mutation(async ({ ctx, input }) => { - await ctx.db.insert(apps).values({ - id: createId(), - name: input.name, - description: input.description, - iconUrl: input.iconUrl, - href: input.href, - }); - }), - update: publicProcedure.input(validation.app.edit).mutation(async ({ ctx, input }) => { + if (!app) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "App not found", + }); + } + + return app; + }), + create: protectedProcedure + .input(validation.app.manage) + .output(z.void()) + .meta({ openapi: { method: "POST", path: "/api/apps", tags: ["apps"], protect: true } }) + .mutation(async ({ ctx, input }) => { + await ctx.db.insert(apps).values({ + id: createId(), + name: input.name, + description: input.description, + iconUrl: input.iconUrl, + href: input.href, + }); + }), + update: protectedProcedure.input(validation.app.edit).mutation(async ({ ctx, input }) => { const app = await ctx.db.query.apps.findFirst({ where: eq(apps.id, input.id), }); @@ -76,7 +137,11 @@ export const appRouter = createTRPCRouter({ }) .where(eq(apps.id, input.id)); }), - delete: publicProcedure.input(validation.common.byId).mutation(async ({ ctx, input }) => { - await ctx.db.delete(apps).where(eq(apps.id, input.id)); - }), + delete: protectedProcedure + .output(z.void()) + .meta({ openapi: { method: "DELETE", path: "/api/apps/{id}", tags: ["apps"], protect: true } }) + .input(validation.common.byId) + .mutation(async ({ ctx, input }) => { + await ctx.db.delete(apps).where(eq(apps.id, input.id)); + }), }); diff --git a/packages/api/src/router/cron-jobs.ts b/packages/api/src/router/cron-jobs.ts index a63c72ff9..e8c582f24 100644 --- a/packages/api/src/router/cron-jobs.ts +++ b/packages/api/src/router/cron-jobs.ts @@ -6,20 +6,23 @@ import { createCronJobStatusChannel } from "@homarr/cron-job-status"; import { jobGroup } from "@homarr/cron-jobs"; import { logger } from "@homarr/log"; -import { createTRPCRouter, publicProcedure } from "../trpc"; +import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; export const cronJobsRouter = createTRPCRouter({ - triggerJob: publicProcedure.input(jobNameSchema).mutation(async ({ input }) => { - await triggerCronJobAsync(input); - }), - getJobs: publicProcedure.query(() => { + triggerJob: permissionRequiredProcedure + .requiresPermission("admin") + .input(jobNameSchema) + .mutation(async ({ input }) => { + await triggerCronJobAsync(input); + }), + getJobs: permissionRequiredProcedure.requiresPermission("admin").query(() => { const registry = jobGroup.getJobRegistry(); return [...registry.values()].map((job) => ({ name: job.name, expression: job.cronExpression, })); }), - subscribeToStatusUpdates: publicProcedure.subscription(() => { + subscribeToStatusUpdates: permissionRequiredProcedure.requiresPermission("admin").subscription(() => { return observable((emit) => { const unsubscribes: (() => void)[] = []; diff --git a/packages/api/src/router/group.ts b/packages/api/src/router/group.ts index 1397abdd0..d14083995 100644 --- a/packages/api/src/router/group.ts +++ b/packages/api/src/router/group.ts @@ -5,84 +5,92 @@ import { and, createId, eq, like, not, sql } from "@homarr/db"; import { groupMembers, groupPermissions, groups } from "@homarr/db/schema/sqlite"; import { validation, z } from "@homarr/validation"; -import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; +import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../trpc"; +import { throwIfCredentialsDisabled } from "./invite/checks"; export const groupRouter = createTRPCRouter({ - getPaginated: protectedProcedure.input(validation.common.paginated).query(async ({ input, ctx }) => { - const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined; - const groupCount = await ctx.db - .select({ - count: sql`count(*)`, - }) - .from(groups) - .where(whereQuery); - - const dbGroups = await ctx.db.query.groups.findMany({ - with: { - members: { - with: { - user: { - columns: { - id: true, - name: true, - email: true, - image: true, + getPaginated: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.common.paginated) + .query(async ({ input, ctx }) => { + const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined; + const groupCount = await ctx.db + .select({ + count: sql`count(*)`, + }) + .from(groups) + .where(whereQuery); + + const dbGroups = await ctx.db.query.groups.findMany({ + with: { + members: { + with: { + user: { + columns: { + id: true, + name: true, + email: true, + image: true, + }, }, }, }, }, - }, - limit: input.pageSize, - offset: (input.page - 1) * input.pageSize, - where: whereQuery, - }); + limit: input.pageSize, + offset: (input.page - 1) * input.pageSize, + where: whereQuery, + }); - return { - items: dbGroups.map((group) => ({ - ...group, - members: group.members.map((member) => member.user), - })), - totalCount: groupCount[0]?.count ?? 0, - }; - }), - getById: protectedProcedure.input(validation.common.byId).query(async ({ input, ctx }) => { - const group = await ctx.db.query.groups.findFirst({ - where: eq(groups.id, input.id), - with: { - members: { - with: { - user: { - columns: { - id: true, - name: true, - email: true, - image: true, - provider: true, + return { + items: dbGroups.map((group) => ({ + ...group, + members: group.members.map((member) => member.user), + })), + totalCount: groupCount[0]?.count ?? 0, + }; + }), + getById: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.common.byId) + .query(async ({ input, ctx }) => { + const group = await ctx.db.query.groups.findFirst({ + where: eq(groups.id, input.id), + with: { + members: { + with: { + user: { + columns: { + id: true, + name: true, + email: true, + image: true, + provider: true, + }, }, }, }, - }, - permissions: { - columns: { - permission: true, + permissions: { + columns: { + permission: true, + }, }, }, - }, - }); - - if (!group) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Group not found", }); - } - return { - ...group, - members: group.members.map((member) => member.user), - permissions: group.permissions.map((permission) => permission.permission), - }; - }), + if (!group) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Group not found", + }); + } + + return { + ...group, + members: group.members.map((member) => member.user), + permissions: group.permissions.map((permission) => permission.permission), + }; + }), + // Is protected because also used in board access / integration access forms selectable: protectedProcedure.query(async ({ ctx }) => { return await ctx.db.query.groups.findMany({ columns: { @@ -91,7 +99,8 @@ export const groupRouter = createTRPCRouter({ }, }); }), - search: publicProcedure + search: permissionRequiredProcedure + .requiresPermission("admin") .input( z.object({ query: z.string(), @@ -108,85 +117,108 @@ export const groupRouter = createTRPCRouter({ limit: input.limit, }); }), - createGroup: protectedProcedure.input(validation.group.create).mutation(async ({ input, ctx }) => { - const normalizedName = normalizeName(input.name); - await checkSimilarNameAndThrowAsync(ctx.db, normalizedName); - - const id = createId(); - await ctx.db.insert(groups).values({ - id, - name: normalizedName, - ownerId: ctx.session.user.id, - }); - - return id; - }), - updateGroup: protectedProcedure.input(validation.group.update).mutation(async ({ input, ctx }) => { - await throwIfGroupNotFoundAsync(ctx.db, input.id); - - const normalizedName = normalizeName(input.name); - await checkSimilarNameAndThrowAsync(ctx.db, normalizedName, input.id); - - await ctx.db - .update(groups) - .set({ + createGroup: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.group.create) + .mutation(async ({ input, ctx }) => { + const normalizedName = normalizeName(input.name); + await checkSimilarNameAndThrowAsync(ctx.db, normalizedName); + + const id = createId(); + await ctx.db.insert(groups).values({ + id, name: normalizedName, - }) - .where(eq(groups.id, input.id)); - }), - savePermissions: protectedProcedure.input(validation.group.savePermissions).mutation(async ({ input, ctx }) => { - await throwIfGroupNotFoundAsync(ctx.db, input.groupId); - - await ctx.db.delete(groupPermissions).where(eq(groupPermissions.groupId, input.groupId)); - - await ctx.db.insert(groupPermissions).values( - input.permissions.map((permission) => ({ - groupId: input.groupId, - permission, - })), - ); - }), - transferOwnership: protectedProcedure.input(validation.group.groupUser).mutation(async ({ input, ctx }) => { - await throwIfGroupNotFoundAsync(ctx.db, input.groupId); - - await ctx.db - .update(groups) - .set({ - ownerId: input.userId, - }) - .where(eq(groups.id, input.groupId)); - }), - deleteGroup: protectedProcedure.input(validation.common.byId).mutation(async ({ input, ctx }) => { - await throwIfGroupNotFoundAsync(ctx.db, input.id); - - await ctx.db.delete(groups).where(eq(groups.id, input.id)); - }), - addMember: protectedProcedure.input(validation.group.groupUser).mutation(async ({ input, ctx }) => { - await throwIfGroupNotFoundAsync(ctx.db, input.groupId); + ownerId: ctx.session.user.id, + }); - const user = await ctx.db.query.users.findFirst({ - where: eq(groups.id, input.userId), - }); + return id; + }), + updateGroup: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.group.update) + .mutation(async ({ input, ctx }) => { + await throwIfGroupNotFoundAsync(ctx.db, input.id); + + const normalizedName = normalizeName(input.name); + await checkSimilarNameAndThrowAsync(ctx.db, normalizedName, input.id); + + await ctx.db + .update(groups) + .set({ + name: normalizedName, + }) + .where(eq(groups.id, input.id)); + }), + savePermissions: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.group.savePermissions) + .mutation(async ({ input, ctx }) => { + await throwIfGroupNotFoundAsync(ctx.db, input.groupId); + + await ctx.db.delete(groupPermissions).where(eq(groupPermissions.groupId, input.groupId)); + + await ctx.db.insert(groupPermissions).values( + input.permissions.map((permission) => ({ + groupId: input.groupId, + permission, + })), + ); + }), + transferOwnership: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.group.groupUser) + .mutation(async ({ input, ctx }) => { + await throwIfGroupNotFoundAsync(ctx.db, input.groupId); + + await ctx.db + .update(groups) + .set({ + ownerId: input.userId, + }) + .where(eq(groups.id, input.groupId)); + }), + deleteGroup: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.common.byId) + .mutation(async ({ input, ctx }) => { + await throwIfGroupNotFoundAsync(ctx.db, input.id); - if (!user) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "User not found", + await ctx.db.delete(groups).where(eq(groups.id, input.id)); + }), + addMember: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.group.groupUser) + .mutation(async ({ input, ctx }) => { + await throwIfGroupNotFoundAsync(ctx.db, input.groupId); + throwIfCredentialsDisabled(); + + const user = await ctx.db.query.users.findFirst({ + where: eq(groups.id, input.userId), }); - } - await ctx.db.insert(groupMembers).values({ - groupId: input.groupId, - userId: input.userId, - }); - }), - removeMember: protectedProcedure.input(validation.group.groupUser).mutation(async ({ input, ctx }) => { - await throwIfGroupNotFoundAsync(ctx.db, input.groupId); + if (!user) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } - await ctx.db - .delete(groupMembers) - .where(and(eq(groupMembers.groupId, input.groupId), eq(groupMembers.userId, input.userId))); - }), + await ctx.db.insert(groupMembers).values({ + groupId: input.groupId, + userId: input.userId, + }); + }), + removeMember: permissionRequiredProcedure + .requiresPermission("admin") + .input(validation.group.groupUser) + .mutation(async ({ input, ctx }) => { + await throwIfGroupNotFoundAsync(ctx.db, input.groupId); + throwIfCredentialsDisabled(); + + await ctx.db + .delete(groupMembers) + .where(and(eq(groupMembers.groupId, input.groupId), eq(groupMembers.userId, input.userId))); + }), }); const normalizeName = (name: string) => name.trim(); diff --git a/packages/api/src/router/home.ts b/packages/api/src/router/home.ts index 191698566..2174a2968 100644 --- a/packages/api/src/router/home.ts +++ b/packages/api/src/router/home.ts @@ -1,17 +1,32 @@ +import type { AnySQLiteTable } from "drizzle-orm/sqlite-core"; + +import { isProviderEnabled } from "@homarr/auth/server"; +import type { Database } from "@homarr/db"; import { count } from "@homarr/db"; import { apps, boards, groups, integrations, invites, users } from "@homarr/db/schema/sqlite"; -import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { createTRPCRouter, publicProcedure } from "../trpc"; export const homeRouter = createTRPCRouter({ - getStats: protectedProcedure.query(async ({ ctx }) => { + getStats: publicProcedure.query(async ({ ctx }) => { + const isAdmin = ctx.session?.user.permissions.includes("admin") ?? false; + const isCredentialsEnabled = isProviderEnabled("credentials"); + return { - countBoards: (await ctx.db.select({ count: count() }).from(boards))[0]?.count ?? 0, - countUsers: (await ctx.db.select({ count: count() }).from(users))[0]?.count ?? 0, - countGroups: (await ctx.db.select({ count: count() }).from(groups))[0]?.count ?? 0, - countInvites: (await ctx.db.select({ count: count() }).from(invites))[0]?.count ?? 0, - countIntegrations: (await ctx.db.select({ count: count() }).from(integrations))[0]?.count ?? 0, - countApps: (await ctx.db.select({ count: count() }).from(apps))[0]?.count ?? 0, + countBoards: await getCountForTableAsync(ctx.db, boards, true), + countUsers: await getCountForTableAsync(ctx.db, users, isAdmin), + countGroups: await getCountForTableAsync(ctx.db, groups, true), + countInvites: await getCountForTableAsync(ctx.db, invites, isAdmin), + countIntegrations: await getCountForTableAsync(ctx.db, integrations, isCredentialsEnabled && isAdmin), + countApps: await getCountForTableAsync(ctx.db, apps, true), }; }), }); + +const getCountForTableAsync = async (db: Database, table: AnySQLiteTable, canView: boolean) => { + if (!canView) { + return 0; + } + + return (await db.select({ count: count() }).from(table))[0]?.count ?? 0; +}; diff --git a/packages/api/src/router/integration/integration-router.ts b/packages/api/src/router/integration/integration-router.ts index 196c30761..00aa72e34 100644 --- a/packages/api/src/router/integration/integration-router.ts +++ b/packages/api/src/router/integration/integration-router.ts @@ -4,6 +4,7 @@ import { decryptSecret, encryptSecret } from "@homarr/common/server"; import type { Database } from "@homarr/db"; import { and, asc, createId, eq, inArray, like } from "@homarr/db"; import { + groupMembers, groupPermissions, integrationGroupPermissions, integrations, @@ -14,20 +15,48 @@ import type { IntegrationSecretKind } from "@homarr/definitions"; import { getPermissionsWithParents, integrationKinds, integrationSecretKindObject } from "@homarr/definitions"; import { validation, z } from "@homarr/validation"; -import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc"; +import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc"; import { throwIfActionForbiddenAsync } from "./integration-access"; import { testConnectionAsync } from "./integration-test-connection"; export const integrationRouter = createTRPCRouter({ - all: protectedProcedure.query(async ({ ctx }) => { - const integrations = await ctx.db.query.integrations.findMany(); + all: publicProcedure.query(async ({ ctx }) => { + const groupsOfCurrentUser = await ctx.db.query.groupMembers.findMany({ + where: eq(groupMembers.userId, ctx.session?.user.id ?? ""), + }); + + const integrations = await ctx.db.query.integrations.findMany({ + with: { + userPermissions: { + where: eq(integrationUserPermissions.userId, ctx.session?.user.id ?? ""), + }, + groupPermissions: { + where: inArray( + integrationGroupPermissions.groupId, + groupsOfCurrentUser.map((group) => group.groupId), + ), + }, + }, + }); return integrations - .map((integration) => ({ - id: integration.id, - name: integration.name, - kind: integration.kind, - url: integration.url, - })) + .map((integration) => { + const permissions = integration.userPermissions + .map(({ permission }) => permission) + .concat(integration.groupPermissions.map(({ permission }) => permission)); + + return { + id: integration.id, + name: integration.name, + kind: integration.kind, + url: integration.url, + permissions: { + hasUseAccess: + permissions.includes("use") || permissions.includes("interact") || permissions.includes("full"), + hasInteractAccess: permissions.includes("interact") || permissions.includes("full"), + hasFullAccess: permissions.includes("full"), + }, + }; + }) .sort( (integrationA, integrationB) => integrationKinds.indexOf(integrationA.kind) - integrationKinds.indexOf(integrationB.kind), diff --git a/packages/api/src/router/log.ts b/packages/api/src/router/log.ts index ba5690657..d48196a19 100644 --- a/packages/api/src/router/log.ts +++ b/packages/api/src/router/log.ts @@ -4,10 +4,10 @@ import { logger } from "@homarr/log"; import type { LoggerMessage } from "@homarr/redis"; import { loggingChannel } from "@homarr/redis"; -import { createTRPCRouter, publicProcedure } from "../trpc"; +import { createTRPCRouter, permissionRequiredProcedure } from "../trpc"; export const logRouter = createTRPCRouter({ - subscribe: publicProcedure.subscription(() => { + subscribe: permissionRequiredProcedure.requiresPermission("admin").subscription(() => { return observable((emit) => { const unsubscribe = loggingChannel.subscribe((data) => { emit.next(data); diff --git a/packages/api/src/router/test/app.spec.ts b/packages/api/src/router/test/app.spec.ts index 1ab6ee559..4fdb07d56 100644 --- a/packages/api/src/router/test/app.spec.ts +++ b/packages/api/src/router/test/app.spec.ts @@ -11,6 +11,11 @@ import { appRouter } from "../app"; // Mock the auth module to return an empty session vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session })); +const defaultSession: Session = { + user: { id: createId(), permissions: [], colorScheme: "light" }, + expires: new Date().toISOString(), +}; + describe("all should return all apps", () => { test("should return all apps", async () => { const db = createDb(); @@ -89,7 +94,7 @@ describe("create should create a new app with all arguments", () => { const db = createDb(); const caller = appRouter.createCaller({ db, - session: null, + session: defaultSession, }); const input = { name: "Mantine", @@ -112,7 +117,7 @@ describe("create should create a new app with all arguments", () => { const db = createDb(); const caller = appRouter.createCaller({ db, - session: null, + session: defaultSession, }); const input = { name: "Mantine", @@ -137,7 +142,7 @@ describe("update should update an app", () => { const db = createDb(); const caller = appRouter.createCaller({ db, - session: null, + session: defaultSession, }); const appId = createId(); @@ -172,7 +177,7 @@ describe("update should update an app", () => { const db = createDb(); const caller = appRouter.createCaller({ db, - session: null, + session: defaultSession, }); const actAsync = async () => @@ -192,7 +197,7 @@ describe("delete should delete an app", () => { const db = createDb(); const caller = appRouter.createCaller({ db, - session: null, + session: defaultSession, }); const appId = createId(); diff --git a/packages/api/src/router/test/group.spec.ts b/packages/api/src/router/test/group.spec.ts index 5e3ca35a4..b552e3bbd 100644 --- a/packages/api/src/router/test/group.spec.ts +++ b/packages/api/src/router/test/group.spec.ts @@ -1,21 +1,26 @@ import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; +import * as env from "@homarr/auth/env.mjs"; import { createId, eq } from "@homarr/db"; import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema/sqlite"; import { createDb } from "@homarr/db/test"; +import type { GroupPermissionKey } from "@homarr/definitions"; import { groupRouter } from "../group"; const defaultOwnerId = createId(); -const defaultSession = { - user: { - id: defaultOwnerId, - permissions: [], - colorScheme: "light", - }, - expires: new Date().toISOString(), -} satisfies Session; +const createSession = (permissions: GroupPermissionKey[]) => + ({ + user: { + id: defaultOwnerId, + permissions, + colorScheme: "light", + }, + expires: new Date().toISOString(), + }) satisfies Session; +const defaultSession = createSession([]); +const adminSession = createSession(["admin"]); // Mock the auth module to return an empty session vi.mock("@homarr/auth", async () => { @@ -32,7 +37,7 @@ describe("paginated should return a list of groups with pagination", () => { async (page, expectedCount) => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values( [1, 2, 3, 4, 5].map((number) => ({ @@ -55,7 +60,7 @@ describe("paginated should return a list of groups with pagination", () => { test("with 5 groups in database and pagesize set to 3 it should return total count 5", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values( [1, 2, 3, 4, 5].map((number) => ({ @@ -76,7 +81,7 @@ describe("paginated should return a list of groups with pagination", () => { test("groups should contain id, name, email and image of members", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const user = createDummyUser(); await db.insert(users).values(user); @@ -112,7 +117,7 @@ describe("paginated should return a list of groups with pagination", () => { async (query, expectedCount, firstKey) => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values( ["first", "second", "third", "forth", "fifth"].map((key, index) => ({ @@ -131,13 +136,25 @@ describe("paginated should return a list of groups with pagination", () => { expect(result.items.at(0)?.name).toBe(firstKey); }, ); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => await caller.getPaginated({}); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("byId should return group by id including members and permissions", () => { test('should return group with id "1" with members and permissions', async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const user = createDummyUser(); const groupId = "1"; @@ -180,7 +197,7 @@ describe("byId should return group by id including members and permissions", () test("with group id 1 and group 2 in database it should throw NOT_FOUND error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values({ id: "2", @@ -193,13 +210,25 @@ describe("byId should return group by id including members and permissions", () // Assert await expect(actAsync()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => await caller.getById({ id: "1" }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("create should create group in database", () => { test("with valid input (64 character name) and non existing name it should be successful", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const name = "a".repeat(64); await db.insert(users).values(defaultSession.user); @@ -223,7 +252,7 @@ describe("create should create group in database", () => { test("with more than 64 characters name it should fail while validation", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const longName = "a".repeat(65); // Act @@ -244,7 +273,7 @@ describe("create should create group in database", () => { ])("with similar name %s it should fail to create %s", async (similarName, nameToCreate) => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values({ id: createId(), @@ -257,6 +286,18 @@ describe("create should create group in database", () => { // Assert await expect(actAsync()).rejects.toThrow("similar name"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => await caller.createGroup({ name: "test" }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("update should update name with value that is no duplicate", () => { @@ -266,7 +307,7 @@ describe("update should update name with value that is no duplicate", () => { ])("update should update name from %s to %s normalized", async (initialValue, updateValue, expectedValue) => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); await db.insert(groups).values([ @@ -299,7 +340,7 @@ describe("update should update name with value that is no duplicate", () => { ])("with similar name %s it should fail to update %s", async (updateValue, initialDuplicate) => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); await db.insert(groups).values([ @@ -327,7 +368,7 @@ describe("update should update name with value that is no duplicate", () => { test("with non existing id it should throw not found error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values({ id: createId(), @@ -344,13 +385,29 @@ describe("update should update name with value that is no duplicate", () => { // Assert await expect(act()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => + await caller.updateGroup({ + id: createId(), + name: "test", + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("savePermissions should save permissions for group", () => { test("with existing group and permissions it should save permissions", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); await db.insert(groups).values({ @@ -380,7 +437,7 @@ describe("savePermissions should save permissions for group", () => { test("with non existing group it should throw not found error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values({ id: createId(), @@ -397,13 +454,29 @@ describe("savePermissions should save permissions for group", () => { // Assert await expect(actAsync()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => + await caller.savePermissions({ + groupId: createId(), + permissions: ["integration-create", "board-full-all"], + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("transferOwnership should transfer ownership of group", () => { test("with existing group and user it should transfer ownership", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); const newUserId = createId(); @@ -440,7 +513,7 @@ describe("transferOwnership should transfer ownership of group", () => { test("with non existing group it should throw not found error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values({ id: createId(), @@ -457,13 +530,29 @@ describe("transferOwnership should transfer ownership of group", () => { // Assert await expect(actAsync()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => + await caller.transferOwnership({ + groupId: createId(), + userId: createId(), + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("deleteGroup should delete group", () => { test("with existing group it should delete group", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); await db.insert(groups).values([ @@ -492,7 +581,7 @@ describe("deleteGroup should delete group", () => { test("with non existing group it should throw not found error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(groups).values({ id: createId(), @@ -508,13 +597,30 @@ describe("deleteGroup should delete group", () => { // Assert await expect(actAsync()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => + await caller.deleteGroup({ + id: createId(), + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); }); describe("addMember should add member to group", () => { test("with existing group and user it should add member", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const spy = vi.spyOn(env, "env", "get"); + spy.mockReturnValue({ AUTH_PROVIDERS: ["credentials"] } as never); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); const userId = createId(); @@ -552,7 +658,7 @@ describe("addMember should add member to group", () => { test("with non existing group it should throw not found error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(users).values({ id: createId(), @@ -569,13 +675,67 @@ describe("addMember should add member to group", () => { // Assert await expect(actAsync()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => + await caller.addMember({ + groupId: createId(), + userId: createId(), + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); + + test("without credentials provider it should throw FORBIDDEN error", async () => { + // Arrange + const db = createDb(); + const spy = vi.spyOn(env, "env", "get"); + spy.mockReturnValue({ AUTH_PROVIDERS: ["ldap"] } as never); + const caller = groupRouter.createCaller({ db, session: adminSession }); + + const groupId = createId(); + const userId = createId(); + await db.insert(users).values([ + { + id: userId, + name: "User", + }, + { + id: defaultOwnerId, + name: "Creator", + }, + ]); + await db.insert(groups).values({ + id: groupId, + name: "Group", + ownerId: defaultOwnerId, + }); + + // Act + const actAsync = async () => + await caller.addMember({ + groupId, + userId, + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Credentials provider is disabled"); + }); }); describe("removeMember should remove member from group", () => { test("with existing group and user it should remove member", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const spy = vi.spyOn(env, "env", "get"); + spy.mockReturnValue({ AUTH_PROVIDERS: ["credentials"] } as never); + const caller = groupRouter.createCaller({ db, session: adminSession }); const groupId = createId(); const userId = createId(); @@ -616,7 +776,7 @@ describe("removeMember should remove member from group", () => { test("with non existing group it should throw not found error", async () => { // Arrange const db = createDb(); - const caller = groupRouter.createCaller({ db, session: defaultSession }); + const caller = groupRouter.createCaller({ db, session: adminSession }); await db.insert(users).values({ id: createId(), @@ -633,6 +793,62 @@ describe("removeMember should remove member from group", () => { // Assert await expect(actAsync()).rejects.toThrow("Group not found"); }); + + test("without admin permissions it should throw unauthorized error", async () => { + // Arrange + const db = createDb(); + const caller = groupRouter.createCaller({ db, session: defaultSession }); + + // Act + const actAsync = async () => + await caller.removeMember({ + groupId: createId(), + userId: createId(), + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Permission denied"); + }); + + test("without credentials provider it should throw FORBIDDEN error", async () => { + // Arrange + const db = createDb(); + const spy = vi.spyOn(env, "env", "get"); + spy.mockReturnValue({ AUTH_PROVIDERS: ["ldap"] } as never); + const caller = groupRouter.createCaller({ db, session: adminSession }); + + const groupId = createId(); + const userId = createId(); + await db.insert(users).values([ + { + id: userId, + name: "User", + }, + { + id: defaultOwnerId, + name: "Creator", + }, + ]); + await db.insert(groups).values({ + id: groupId, + name: "Group", + ownerId: defaultOwnerId, + }); + await db.insert(groupMembers).values({ + groupId, + userId, + }); + + // Act + const actAsync = async () => + await caller.removeMember({ + groupId, + userId, + }); + + // Assert + await expect(actAsync()).rejects.toThrow("Credentials provider is disabled"); + }); }); const createDummyUser = () => ({ diff --git a/packages/api/src/router/test/user.spec.ts b/packages/api/src/router/test/user.spec.ts index e27074426..f19502bd5 100644 --- a/packages/api/src/router/test/user.spec.ts +++ b/packages/api/src/router/test/user.spec.ts @@ -4,9 +4,22 @@ import type { Session } from "@homarr/auth"; import { createId, eq, schema } from "@homarr/db"; import { users } from "@homarr/db/schema/sqlite"; import { createDb } from "@homarr/db/test"; +import type { GroupPermissionKey } from "@homarr/definitions"; import { userRouter } from "../user"; +const defaultOwnerId = createId(); +const createSession = (permissions: GroupPermissionKey[]) => + ({ + user: { + id: defaultOwnerId, + permissions, + colorScheme: "light", + }, + expires: new Date().toISOString(), + }) satisfies Session; +const defaultSession = createSession([]); + // Mock the auth module to return an empty session vi.mock("@homarr/auth", async () => { const mod = await import("@homarr/auth/security"); @@ -212,14 +225,13 @@ describe("editProfile shoud update user", () => { const db = createDb(); const caller = userRouter.createCaller({ db, - session: null, + session: defaultSession, }); - const id = createId(); const emailVerified = new Date(2024, 0, 5); await db.insert(schema.users).values({ - id, + id: defaultOwnerId, name: "TEST 1", email: "abc@gmail.com", emailVerified, @@ -227,17 +239,17 @@ describe("editProfile shoud update user", () => { // act await caller.editProfile({ - id: id, + id: defaultOwnerId, name: "ABC", email: "", }); // assert - const user = await db.select().from(schema.users).where(eq(schema.users.id, id)); + const user = await db.select().from(schema.users).where(eq(schema.users.id, defaultOwnerId)); expect(user).toHaveLength(1); expect(user[0]).toStrictEqual({ - id, + id: defaultOwnerId, name: "ABC", email: "abc@gmail.com", emailVerified, @@ -247,6 +259,7 @@ describe("editProfile shoud update user", () => { homeBoardId: null, provider: "credentials", colorScheme: "auto", + firstDayOfWeek: 1, }); }); @@ -255,13 +268,11 @@ describe("editProfile shoud update user", () => { const db = createDb(); const caller = userRouter.createCaller({ db, - session: null, + session: defaultSession, }); - const id = createId(); - await db.insert(schema.users).values({ - id, + id: defaultOwnerId, name: "TEST 1", email: "abc@gmail.com", emailVerified: new Date(2024, 0, 5), @@ -269,17 +280,17 @@ describe("editProfile shoud update user", () => { // act await caller.editProfile({ - id, + id: defaultOwnerId, name: "ABC", email: "myNewEmail@gmail.com", }); // assert - const user = await db.select().from(schema.users).where(eq(schema.users.id, id)); + const user = await db.select().from(schema.users).where(eq(schema.users.id, defaultOwnerId)); expect(user).toHaveLength(1); expect(user[0]).toStrictEqual({ - id, + id: defaultOwnerId, name: "ABC", email: "myNewEmail@gmail.com", emailVerified: null, @@ -289,6 +300,7 @@ describe("editProfile shoud update user", () => { homeBoardId: null, provider: "credentials", colorScheme: "auto", + firstDayOfWeek: 1, }); }); }); @@ -298,11 +310,9 @@ describe("delete should delete user", () => { const db = createDb(); const caller = userRouter.createCaller({ db, - session: null, + session: defaultSession, }); - const userToDelete = createId(); - const initialUsers = [ { id: createId(), @@ -315,9 +325,10 @@ describe("delete should delete user", () => { homeBoardId: null, provider: "ldap" as const, colorScheme: "auto" as const, + firstDayOfWeek: 1 as const, }, { - id: userToDelete, + id: defaultOwnerId, name: "User 2", email: null, emailVerified: null, @@ -326,6 +337,7 @@ describe("delete should delete user", () => { salt: null, homeBoardId: null, colorScheme: "auto" as const, + firstDayOfWeek: 1 as const, }, { id: createId(), @@ -338,12 +350,13 @@ describe("delete should delete user", () => { homeBoardId: null, provider: "oidc" as const, colorScheme: "auto" as const, + firstDayOfWeek: 1 as const, }, ]; await db.insert(schema.users).values(initialUsers); - await caller.delete(userToDelete); + await caller.delete(defaultOwnerId); const usersInDb = await db.select().from(schema.users); expect(usersInDb).toStrictEqual([initialUsers[0], initialUsers[2]]); diff --git a/packages/api/src/router/user.ts b/packages/api/src/router/user.ts index 4d10a4652..d1a9719f3 100644 --- a/packages/api/src/router/user.ts +++ b/packages/api/src/router/user.ts @@ -8,7 +8,7 @@ import type { SupportedAuthProvider } from "@homarr/definitions"; import { logger } from "@homarr/log"; import { validation, z } from "@homarr/validation"; -import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; +import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc"; import { throwIfCredentialsDisabled } from "./invite/checks"; export const userRouter = createTRPCRouter({ @@ -69,8 +69,9 @@ export const userRouter = createTRPCRouter({ // Delete invite as it's used await ctx.db.delete(invites).where(inviteWhere); }), - create: publicProcedure - .meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"] } }) + create: permissionRequiredProcedure + .requiresPermission("admin") + .meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } }) .input(validation.user.create) .output(z.void()) .mutation(async ({ ctx, input }) => { @@ -130,7 +131,8 @@ export const userRouter = createTRPCRouter({ }) .where(eq(users.id, input.userId)); }), - getAll: publicProcedure + getAll: permissionRequiredProcedure + .requiresPermission("admin") .input(z.void()) .output( z.array( @@ -143,7 +145,7 @@ export const userRouter = createTRPCRouter({ }), ), ) - .meta({ openapi: { method: "GET", path: "/api/users", tags: ["users"] } }) + .meta({ openapi: { method: "GET", path: "/api/users", tags: ["users"], protect: true } }) .query(({ ctx }) => { return ctx.db.query.users.findMany({ columns: { @@ -155,7 +157,8 @@ export const userRouter = createTRPCRouter({ }, }); }), - selectable: publicProcedure.query(({ ctx }) => { + // Is protected because also used in board access / integration access forms + selectable: protectedProcedure.query(({ ctx }) => { return ctx.db.query.users.findMany({ columns: { id: true, @@ -164,7 +167,8 @@ export const userRouter = createTRPCRouter({ }, }); }), - search: publicProcedure + search: permissionRequiredProcedure + .requiresPermission("admin") .input( z.object({ query: z.string(), @@ -187,7 +191,14 @@ export const userRouter = createTRPCRouter({ image: user.image, })); }), - getById: publicProcedure.input(z.object({ userId: z.string() })).query(async ({ input, ctx }) => { + getById: protectedProcedure.input(z.object({ userId: z.string() })).query(async ({ input, ctx }) => { + // Only admins can view other users details + if (ctx.session.user.id !== input.userId && !ctx.session.user.permissions.includes("admin")) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not allowed to view other users details", + }); + } const user = await ctx.db.query.users.findFirst({ columns: { id: true, @@ -197,6 +208,7 @@ export const userRouter = createTRPCRouter({ image: true, provider: true, homeBoardId: true, + firstDayOfWeek: true, }, where: eq(users.id, input.userId), }); @@ -210,7 +222,15 @@ export const userRouter = createTRPCRouter({ return user; }), - editProfile: publicProcedure.input(validation.user.editProfile).mutation(async ({ input, ctx }) => { + editProfile: protectedProcedure.input(validation.user.editProfile).mutation(async ({ input, ctx }) => { + // Only admins can view other users details + if (ctx.session.user.id !== input.id && !ctx.session.user.permissions.includes("admin")) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not allowed to edit other users details", + }); + } + const user = await ctx.db.query.users.findFirst({ columns: { email: true, provider: true }, where: eq(users.id, input.id), @@ -242,7 +262,15 @@ export const userRouter = createTRPCRouter({ }) .where(eq(users.id, input.id)); }), - delete: publicProcedure.input(z.string()).mutation(async ({ input, ctx }) => { + delete: protectedProcedure.input(z.string()).mutation(async ({ input, ctx }) => { + // Only admins and user itself can delete a user + if (ctx.session.user.id !== input && !ctx.session.user.permissions.includes("admin")) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You are not allowed to delete other users", + }); + } + await ctx.db.delete(users).where(eq(users.id, input)); }), changePassword: protectedProcedure.input(validation.user.changePasswordApi).mutation(async ({ ctx, input }) => { @@ -311,7 +339,7 @@ export const userRouter = createTRPCRouter({ .input(validation.user.changeHomeBoard.and(z.object({ userId: z.string() }))) .mutation(async ({ input, ctx }) => { const user = ctx.session.user; - // Only admins can change other users' passwords + // Only admins can change other users passwords if (!user.permissions.includes("admin") && user.id !== input.userId) { throw new TRPCError({ code: "NOT_FOUND", @@ -348,6 +376,53 @@ export const userRouter = createTRPCRouter({ }) .where(eq(users.id, ctx.session.user.id)); }), + getFirstDayOfWeekForUserOrDefault: publicProcedure.query(async ({ ctx }) => { + if (!ctx.session?.user) { + return 1 as const; + } + + const user = await ctx.db.query.users.findFirst({ + columns: { + id: true, + firstDayOfWeek: true, + }, + where: eq(users.id, ctx.session.user.id), + }); + + return user?.firstDayOfWeek ?? (1 as const); + }), + changeFirstDayOfWeek: protectedProcedure + .input(validation.user.firstDayOfWeek.and(validation.common.byId)) + .mutation(async ({ input, ctx }) => { + // Only admins can change other users' passwords + if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } + + const dbUser = await ctx.db.query.users.findFirst({ + columns: { + id: true, + }, + where: eq(users.id, input.id), + }); + + if (!dbUser) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "User not found", + }); + } + + await ctx.db + .update(users) + .set({ + firstDayOfWeek: input.firstDayOfWeek, + }) + .where(eq(users.id, ctx.session.user.id)); + }), }); const createUserAsync = async (db: Database, input: z.infer) => { diff --git a/packages/auth/adapter.ts b/packages/auth/adapter.ts index 4c2929613..71b4ed176 100644 --- a/packages/auth/adapter.ts +++ b/packages/auth/adapter.ts @@ -1,5 +1,45 @@ +import type { Adapter } from "@auth/core/adapters"; import { DrizzleAdapter } from "@auth/drizzle-adapter"; -import { db } from "@homarr/db"; +import type { Database } from "@homarr/db"; +import { and, eq } from "@homarr/db"; +import { accounts, users } from "@homarr/db/schema/sqlite"; +import type { SupportedAuthProvider } from "@homarr/definitions"; -export const adapter = DrizzleAdapter(db); +export const createAdapter = (db: Database, provider: SupportedAuthProvider | "unknown"): Adapter => { + const drizzleAdapter = DrizzleAdapter(db, { usersTable: users, accountsTable: accounts }); + + return { + ...drizzleAdapter, + // We override the default implementation as we want to have a provider + // flag in the user instead of the account to not intermingle users from different providers + // eslint-disable-next-line no-restricted-syntax + getUserByEmail: async (email) => { + if (provider === "unknown") { + throw new Error("Unable to get user by email for unknown provider"); + } + + const user = await db.query.users.findFirst({ + where: and(eq(users.email, email), eq(users.provider, provider)), + columns: { + id: true, + name: true, + email: true, + emailVerified: true, + image: true, + }, + }); + + if (!user) { + return null; + } + + return { + ...user, + // We allow null as email for credentials provider + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + email: user.email!, + }; + }, + }; +}; diff --git a/packages/auth/callbacks.ts b/packages/auth/callbacks.ts index 35152cdc0..6a381f57a 100644 --- a/packages/auth/callbacks.ts +++ b/packages/auth/callbacks.ts @@ -1,16 +1,12 @@ -import { cookies } from "next/headers"; -import type { Adapter } from "@auth/core/adapters"; import dayjs from "dayjs"; import type { NextAuthConfig } from "next-auth"; +import type { Session } from "@homarr/auth"; import type { Database } from "@homarr/db"; import { eq, inArray } from "@homarr/db"; import { groupMembers, groupPermissions, users } from "@homarr/db/schema/sqlite"; import { getPermissionsWithChildren } from "@homarr/definitions"; -import { env } from "./env.mjs"; -import { expireDateAfter, generateSessionToken, sessionTokenCookieName } from "./session"; - export const getCurrentUserPermissionsAsync = async (db: Database, userId: string) => { const dbGroupMembers = await db.query.groupMembers.findMany({ where: eq(groupMembers.userId, userId), @@ -30,6 +26,21 @@ export const getCurrentUserPermissionsAsync = async (db: Database, userId: strin return getPermissionsWithChildren(permissionKeys); }; +export const createSessionAsync = async ( + db: Database, + user: { id: string; email: string | null }, +): Promise => { + return { + expires: dayjs().add(1, "day").toISOString(), + user: { + ...user, + email: user.email ?? "", + permissions: await getCurrentUserPermissionsAsync(db, user.id), + colorScheme: "auto", + }, + } as Session; +}; + export const createSessionCallback = (db: Database): NextAuthCallbackOf<"session"> => { return async ({ session, user }) => { const additionalProperties = await db.query.users.findFirst({ @@ -52,51 +63,6 @@ export const createSessionCallback = (db: Database): NextAuthCallbackOf<"session }; }; -export const createSignInCallback = - (adapter: Adapter, db: Database, isCredentialsRequest: boolean): NextAuthCallbackOf<"signIn"> => - async ({ user }) => { - if (!isCredentialsRequest) return true; - - // https://github.com/nextauthjs/next-auth/issues/6106 - if (!adapter.createSession || !user.id) { - return false; - } - - const sessionToken = generateSessionToken(); - const sessionExpires = expireDateAfter(env.AUTH_SESSION_EXPIRY_TIME); - - await adapter.createSession({ - sessionToken, - userId: user.id, - expires: sessionExpires, - }); - - cookies().set(sessionTokenCookieName, sessionToken, { - path: "/", - expires: sessionExpires, - httpOnly: true, - sameSite: "lax", - secure: true, - }); - - const dbUser = await db.query.users.findFirst({ - where: eq(users.id, user.id), - columns: { - colorScheme: true, - }, - }); - - if (!dbUser) return false; - - // We use a cookie as localStorage is not shared with server (otherwise flickering would occur) - cookies().set("homarr-color-scheme", dbUser.colorScheme, { - path: "/", - expires: dayjs().add(1, "year").toDate(), - }); - - return true; - }; - type NextAuthCallbackRecord = Exclude; export type NextAuthCallbackOf = Exclude< NextAuthCallbackRecord[TKey], diff --git a/packages/auth/configuration.ts b/packages/auth/configuration.ts index a3945a064..3a3fee360 100644 --- a/packages/auth/configuration.ts +++ b/packages/auth/configuration.ts @@ -4,18 +4,21 @@ import NextAuth from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { db } from "@homarr/db"; +import type { SupportedAuthProvider } from "@homarr/definitions"; -import { adapter } from "./adapter"; -import { createSessionCallback, createSignInCallback } from "./callbacks"; +import { createAdapter } from "./adapter"; +import { createSessionCallback } from "./callbacks"; import { env } from "./env.mjs"; -import { createCredentialsConfiguration } from "./providers/credentials/credentials-provider"; +import { createSignInEventHandler } from "./events"; +import { createCredentialsConfiguration, createLdapConfiguration } from "./providers/credentials/credentials-provider"; import { EmptyNextAuthProvider } from "./providers/empty/empty-provider"; import { filterProviders } from "./providers/filter-providers"; import { OidcProvider } from "./providers/oidc/oidc-provider"; import { createRedirectUri } from "./redirect"; -import { sessionTokenCookieName } from "./session"; +import { generateSessionToken, sessionTokenCookieName } from "./session"; -export const createConfiguration = (isCredentialsRequest: boolean, headers: ReadonlyHeaders | null) => +// See why it's unknown in the [...nextauth]/route.ts file +export const createConfiguration = (provider: SupportedAuthProvider | "unknown", headers: ReadonlyHeaders | null) => NextAuth({ logger: { error: (code, ...message) => { @@ -30,21 +33,25 @@ export const createConfiguration = (isCredentialsRequest: boolean, headers: Read }, }, trustHost: true, - adapter, + adapter: createAdapter(db, provider), providers: filterProviders([ Credentials(createCredentialsConfiguration(db)), + Credentials(createLdapConfiguration(db)), EmptyNextAuthProvider(), OidcProvider(headers), ]), callbacks: { session: createSessionCallback(db), - signIn: createSignInCallback(adapter, db, isCredentialsRequest), + }, + events: { + signIn: createSignInEventHandler(db), }, redirectProxyUrl: createRedirectUri(headers, "/api/auth"), secret: "secret-is-not-defined-yet", // TODO: This should be added later session: { strategy: "database", maxAge: env.AUTH_SESSION_EXPIRY_TIME, + generateSessionToken, }, pages: { signIn: "/auth/login", diff --git a/packages/auth/env.mjs b/packages/auth/env.mjs index d8f7b3121..de1faa36f 100644 --- a/packages/auth/env.mjs +++ b/packages/auth/env.mjs @@ -74,6 +74,7 @@ export const env = createEnv({ AUTH_OIDC_CLIENT_NAME: z.string().min(1).default("OIDC"), AUTH_OIDC_AUTO_LOGIN: booleanSchema, AUTH_OIDC_SCOPE_OVERWRITE: z.string().min(1).default("openid email profile groups"), + AUTH_OIDC_GROUPS_ATTRIBUTE: z.string().default("groups"), // Is used in the signIn event to assign the correct groups, key is from object of decoded id_token } : {}), ...(authProviders.includes("ldap") @@ -113,6 +114,7 @@ export const env = createEnv({ AUTH_OIDC_CLIENT_SECRET: process.env.AUTH_OIDC_CLIENT_SECRET, AUTH_OIDC_ISSUER: process.env.AUTH_OIDC_ISSUER, AUTH_OIDC_SCOPE_OVERWRITE: process.env.AUTH_OIDC_SCOPE_OVERWRITE, + AUTH_OIDC_GROUPS_ATTRIBUTE: process.env.AUTH_OIDC_GROUPS_ATTRIBUTE, AUTH_LDAP_USERNAME_ATTRIBUTE: process.env.AUTH_LDAP_USERNAME_ATTRIBUTE, AUTH_LDAP_USER_MAIL_ATTRIBUTE: process.env.AUTH_LDAP_USER_MAIL_ATTRIBUTE, AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG: process.env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG, diff --git a/packages/auth/events.ts b/packages/auth/events.ts new file mode 100644 index 000000000..f4cd1eeac --- /dev/null +++ b/packages/auth/events.ts @@ -0,0 +1,131 @@ +import { cookies } from "next/headers"; +import dayjs from "dayjs"; +import type { NextAuthConfig } from "next-auth"; + +import { and, eq, inArray } from "@homarr/db"; +import type { Database } from "@homarr/db"; +import { groupMembers, groups, users } from "@homarr/db/schema/sqlite"; +import { logger } from "@homarr/log"; + +import { env } from "./env.mjs"; + +export const createSignInEventHandler = (db: Database): Exclude["signIn"] => { + return async ({ user, profile }) => { + if (!user.id) throw new Error("User ID is missing"); + + const dbUser = await db.query.users.findFirst({ + where: eq(users.id, user.id), + columns: { + name: true, + colorScheme: true, + }, + }); + + if (!dbUser) throw new Error("User not found"); + + const groupsKey = env.AUTH_OIDC_GROUPS_ATTRIBUTE; + // Groups from oidc provider are provided from the profile, it's not typed. + if (profile && groupsKey in profile && Array.isArray(profile[groupsKey])) { + await synchronizeGroupsWithExternalForUserAsync(db, user.id, profile[groupsKey] as string[]); + } + + // In ldap-authroization we return the groups from ldap, it's not typed. + if ("groups" in user && Array.isArray(user.groups)) { + await synchronizeGroupsWithExternalForUserAsync(db, user.id, user.groups as string[]); + } + + if (dbUser.name !== user.name) { + await db.update(users).set({ name: user.name }).where(eq(users.id, user.id)); + logger.info( + `Username for user of credentials provider has changed. user=${user.id} old=${dbUser.name} new=${user.name}`, + ); + } + + const profileUsername = profile?.preferred_username?.includes("@") ? profile.name : profile?.preferred_username; + if (profileUsername && dbUser.name !== profileUsername) { + await db.update(users).set({ name: profileUsername }).where(eq(users.id, user.id)); + logger.info( + `Username for user of oidc provider has changed. user=${user.id} old='${dbUser.name}' new='${profileUsername}'`, + ); + } + + // We use a cookie as localStorage is not shared with server (otherwise flickering would occur) + cookies().set("homarr-color-scheme", dbUser.colorScheme, { + path: "/", + expires: dayjs().add(1, "year").toDate(), + }); + }; +}; + +const synchronizeGroupsWithExternalForUserAsync = async (db: Database, userId: string, externalGroups: string[]) => { + const dbGroupMembers = await db.query.groupMembers.findMany({ + where: eq(groupMembers.userId, userId), + with: { + group: { columns: { name: true } }, + }, + }); + + /** + * The below groups are those groups the user is part of in the external system, but not in Homarr. + * So he has to be added to those groups. + */ + const missingExternalGroupsForUser = externalGroups.filter( + (externalGroup) => !dbGroupMembers.some(({ group }) => group.name === externalGroup), + ); + + if (missingExternalGroupsForUser.length > 0) { + logger.debug( + `Homarr does not have the user in certain groups. user=${userId} count=${missingExternalGroupsForUser.length}`, + ); + + const groupIds = await db.query.groups.findMany({ + columns: { + id: true, + }, + where: inArray(groups.name, missingExternalGroupsForUser), + }); + + logger.debug(`Homarr has found groups in the database user is not in. user=${userId} count=${groupIds.length}`); + + if (groupIds.length > 0) { + await db.insert(groupMembers).values( + groupIds.map((group) => ({ + userId, + groupId: group.id, + })), + ); + + logger.info(`Added user to groups successfully. user=${userId} count=${groupIds.length}`); + } else { + logger.debug(`User is already in all groups of Homarr. user=${userId}`); + } + } + + /** + * The below groups are those groups the user is part of in Homarr, but not in the external system. + * So he has to be removed from those groups. + */ + const groupsUserIsNoLongerMemberOfExternally = dbGroupMembers.filter( + ({ group }) => !externalGroups.includes(group.name), + ); + + if (groupsUserIsNoLongerMemberOfExternally.length > 0) { + logger.debug( + `Homarr has the user in certain groups that LDAP does not have. user=${userId} count=${groupsUserIsNoLongerMemberOfExternally.length}`, + ); + + await db.delete(groupMembers).where( + and( + eq(groupMembers.userId, userId), + inArray( + groupMembers.groupId, + groupsUserIsNoLongerMemberOfExternally.map(({ groupId }) => groupId), + ), + ), + ); + + logger.info( + `Removed user from groups successfully. user=${userId} count=${groupsUserIsNoLongerMemberOfExternally.length}`, + ); + } +}; diff --git a/packages/auth/index.ts b/packages/auth/index.ts index 1cb226fa2..877574b84 100644 --- a/packages/auth/index.ts +++ b/packages/auth/index.ts @@ -1,7 +1,7 @@ import { headers } from "next/headers"; import type { DefaultSession } from "@auth/core/types"; -import type { ColorScheme, GroupPermissionKey } from "@homarr/definitions"; +import type { ColorScheme, GroupPermissionKey, SupportedAuthProvider } from "@homarr/definitions"; import { createConfiguration } from "./configuration"; @@ -19,6 +19,7 @@ declare module "next-auth" { export * from "./security"; -export const createHandlers = (isCredentialsRequest: boolean) => createConfiguration(isCredentialsRequest, headers()); +// See why it's unknown in the [...nextauth]/route.ts file +export const createHandlers = (provider: SupportedAuthProvider | "unknown") => createConfiguration(provider, headers()); export { getSessionFromTokenAsync as getSessionFromToken, sessionTokenCookieName } from "./session"; diff --git a/packages/auth/next.ts b/packages/auth/next.ts index 11b66c4cd..c1bb37752 100644 --- a/packages/auth/next.ts +++ b/packages/auth/next.ts @@ -2,7 +2,7 @@ import { cache } from "react"; import { createConfiguration } from "./configuration"; -const { auth: defaultAuth } = createConfiguration(false, null); +const { auth: defaultAuth } = createConfiguration("unknown", null); /** * This is the main way to get session data for your RSCs. diff --git a/packages/auth/package.json b/packages/auth/package.json index ed392b13b..56c14c80f 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -23,8 +23,8 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { - "@auth/core": "^0.35.3", - "@auth/drizzle-adapter": "^1.5.3", + "@auth/core": "^0.37.0", + "@auth/drizzle-adapter": "^1.7.0", "@homarr/common": "workspace:^0.1.0", "@homarr/db": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", @@ -34,7 +34,7 @@ "bcrypt": "^5.1.1", "cookies": "^0.9.1", "ldapts": "7.2.1", - "next": "^14.2.14", + "next": "^14.2.15", "next-auth": "5.0.0-beta.22", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -45,8 +45,8 @@ "@homarr/tsconfig": "workspace:^0.1.0", "@types/bcrypt": "5.0.2", "@types/cookies": "0.9.0", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "prettier": "^3.3.3", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/packages/auth/permissions/integration-permissions.ts b/packages/auth/permissions/integration-permissions.ts index 0ab30322d..cde191996 100644 --- a/packages/auth/permissions/integration-permissions.ts +++ b/packages/auth/permissions/integration-permissions.ts @@ -12,15 +12,17 @@ export interface IntegrationPermissionsProps { } export const constructIntegrationPermissions = (integration: IntegrationPermissionsProps, session: Session | null) => { + const permissions = integration.userPermissions + .concat(integration.groupPermissions) + .map(({ permission }) => permission); + return { - hasFullAccess: session?.user.permissions.includes("integration-full-all") ?? false, + hasFullAccess: + (session?.user.permissions.includes("integration-full-all") ?? false) || permissions.includes("full"), hasInteractAccess: - integration.userPermissions.some(({ permission }) => permission === "interact") || - integration.groupPermissions.some(({ permission }) => permission === "interact") || + permissions.includes("full") || + permissions.includes("interact") || (session?.user.permissions.includes("integration-interact-all") ?? false), - hasUseAccess: - integration.userPermissions.length >= 1 || - integration.groupPermissions.length >= 1 || - (session?.user.permissions.includes("integration-use-all") ?? false), + hasUseAccess: permissions.length >= 1 || (session?.user.permissions.includes("integration-use-all") ?? false), }; }; diff --git a/packages/auth/providers/credentials/authorization/ldap-authorization.ts b/packages/auth/providers/credentials/authorization/ldap-authorization.ts index d54101898..cae75f693 100644 --- a/packages/auth/providers/credentials/authorization/ldap-authorization.ts +++ b/packages/auth/providers/credentials/authorization/ldap-authorization.ts @@ -1,8 +1,8 @@ import { CredentialsSignin } from "@auth/core/errors"; import type { Database, InferInsertModel } from "@homarr/db"; -import { and, createId, eq, inArray } from "@homarr/db"; -import { groupMembers, groups, users } from "@homarr/db/schema/sqlite"; +import { and, createId, eq } from "@homarr/db"; +import { users } from "@homarr/db/schema/sqlite"; import { logger } from "@homarr/log"; import type { validation } from "@homarr/validation"; import { z } from "@homarr/validation"; @@ -99,18 +99,6 @@ export const authorizeWithLdapCredentialsAsync = async ( emailVerified: true, provider: true, }, - with: { - groups: { - with: { - group: { - columns: { - id: true, - name: true, - }, - }, - }, - }, - }, where: and(eq(users.email, mailResult.data), eq(users.provider, "ldap")), }); @@ -128,79 +116,16 @@ export const authorizeWithLdapCredentialsAsync = async ( await db.insert(users).values(insertUser); - user = { - ...insertUser, - groups: [], - }; + user = insertUser; logger.info(`User ${credentials.name} created successfully.`); } - if (user.name !== credentials.name) { - logger.warn(`User ${credentials.name} found in the database but with different name. Updating...`); - - user.name = credentials.name; - - await db.update(users).set({ name: user.name }).where(eq(users.id, user.id)); - - logger.info(`User ${credentials.name} updated successfully.`); - } - - const ldapGroupsUserIsNotIn = userGroups.filter( - (group) => !user.groups.some((userGroup) => userGroup.group.name === group), - ); - - if (ldapGroupsUserIsNotIn.length > 0) { - logger.debug( - `Homarr does not have the user in certain groups. user=${user.name} count=${ldapGroupsUserIsNotIn.length}`, - ); - - const groupIds = await db.query.groups.findMany({ - columns: { - id: true, - }, - where: inArray(groups.name, ldapGroupsUserIsNotIn), - }); - - logger.debug(`Homarr has found groups in the database user is not in. user=${user.name} count=${groupIds.length}`); - - if (groupIds.length > 0) { - await db.insert(groupMembers).values( - groupIds.map((group) => ({ - userId: user.id, - groupId: group.id, - })), - ); - - logger.info(`Added user to groups successfully. user=${user.name} count=${groupIds.length}`); - } else { - logger.debug(`User is already in all groups of Homarr. user=${user.name}`); - } - } - - const homarrGroupsUserIsNotIn = user.groups.filter((userGroup) => !userGroups.includes(userGroup.group.name)); - - if (homarrGroupsUserIsNotIn.length > 0) { - logger.debug( - `Homarr has the user in certain groups that LDAP does not have. user=${user.name} count=${homarrGroupsUserIsNotIn.length}`, - ); - - await db.delete(groupMembers).where( - and( - eq(groupMembers.userId, user.id), - inArray( - groupMembers.groupId, - homarrGroupsUserIsNotIn.map(({ groupId }) => groupId), - ), - ), - ); - - logger.info(`Removed user from groups successfully. user=${user.name} count=${homarrGroupsUserIsNotIn.length}`); - } - return { id: user.id, - name: user.name, + name: credentials.name, + // Groups is used in events.ts to synchronize groups with external systems + groups: userGroups, }; }; diff --git a/packages/auth/providers/credentials/credentials-provider.ts b/packages/auth/providers/credentials/credentials-provider.ts index 7acda2749..571541b7a 100644 --- a/packages/auth/providers/credentials/credentials-provider.ts +++ b/packages/auth/providers/credentials/credentials-provider.ts @@ -10,30 +10,25 @@ type CredentialsConfiguration = Parameters[0]; export const createCredentialsConfiguration = (db: Database) => ({ + id: "credentials", type: "credentials", name: "Credentials", - credentials: { - name: { - label: "Username", - type: "text", - }, - password: { - label: "Password", - type: "password", - }, - isLdap: { - label: "LDAP", - type: "checkbox", - }, - }, // eslint-disable-next-line no-restricted-syntax async authorize(credentials) { const data = await validation.user.signIn.parseAsync(credentials); - if (data.credentialType === "ldap") { - return await authorizeWithLdapCredentialsAsync(db, data).catch(() => null); - } - return await authorizeWithBasicCredentialsAsync(db, data); }, }) satisfies CredentialsConfiguration; + +export const createLdapConfiguration = (db: Database) => + ({ + id: "ldap", + type: "credentials", + name: "Ldap", + // eslint-disable-next-line no-restricted-syntax + async authorize(credentials) { + const data = await validation.user.signIn.parseAsync(credentials); + return await authorizeWithLdapCredentialsAsync(db, data).catch(() => null); + }, + }) satisfies CredentialsConfiguration; diff --git a/packages/auth/providers/test/basic-authorization.spec.ts b/packages/auth/providers/test/basic-authorization.spec.ts index ac070d024..b828d2784 100644 --- a/packages/auth/providers/test/basic-authorization.spec.ts +++ b/packages/auth/providers/test/basic-authorization.spec.ts @@ -25,7 +25,6 @@ describe("authorizeWithBasicCredentials", () => { const result = await authorizeWithBasicCredentialsAsync(db, { name: "test", password: "test", - credentialType: "basic", }); // Assert @@ -47,7 +46,6 @@ describe("authorizeWithBasicCredentials", () => { const result = await authorizeWithBasicCredentialsAsync(db, { name: "test", password: "wrong", - credentialType: "basic", }); // Assert @@ -69,7 +67,6 @@ describe("authorizeWithBasicCredentials", () => { const result = await authorizeWithBasicCredentialsAsync(db, { name: "wrong", password: "test", - credentialType: "basic", }); // Assert @@ -88,7 +85,6 @@ describe("authorizeWithBasicCredentials", () => { const result = await authorizeWithBasicCredentialsAsync(db, { name: "test", password: "test", - credentialType: "basic", }); // Assert diff --git a/packages/auth/providers/test/ldap-authorization.spec.ts b/packages/auth/providers/test/ldap-authorization.spec.ts index 16f2fa697..c78fba30f 100644 --- a/packages/auth/providers/test/ldap-authorization.spec.ts +++ b/packages/auth/providers/test/ldap-authorization.spec.ts @@ -3,7 +3,7 @@ import { describe, expect, test, vi } from "vitest"; import type { Database } from "@homarr/db"; import { and, createId, eq } from "@homarr/db"; -import { groupMembers, groups, users } from "@homarr/db/schema/sqlite"; +import { groups, users } from "@homarr/db/schema/sqlite"; import { createDb } from "@homarr/db/test"; import { authorizeWithLdapCredentialsAsync } from "../credentials/authorization/ldap-authorization"; @@ -34,7 +34,6 @@ describe("authorizeWithLdapCredentials", () => { authorizeWithLdapCredentialsAsync(null as unknown as Database, { name: "test", password: "test", - credentialType: "ldap", }); // Assert @@ -57,7 +56,6 @@ describe("authorizeWithLdapCredentials", () => { authorizeWithLdapCredentialsAsync(null as unknown as Database, { name: "test", password: "test", - credentialType: "ldap", }); // Assert @@ -87,7 +85,6 @@ describe("authorizeWithLdapCredentials", () => { authorizeWithLdapCredentialsAsync(null as unknown as Database, { name: "test", password: "test", - credentialType: "ldap", }); // Assert @@ -120,7 +117,6 @@ describe("authorizeWithLdapCredentials", () => { authorizeWithLdapCredentialsAsync(null as unknown as Database, { name: "test", password: "test", - credentialType: "ldap", }); // Assert @@ -152,11 +148,11 @@ describe("authorizeWithLdapCredentials", () => { const result = await authorizeWithLdapCredentialsAsync(db, { name: "test", password: "test", - credentialType: "ldap", }); // Assert expect(result.name).toBe("test"); + expect(result.groups).toHaveLength(0); // Groups are needed in signIn events callback const dbUser = await db.query.users.findFirst({ where: eq(users.name, "test"), }); @@ -197,11 +193,11 @@ describe("authorizeWithLdapCredentials", () => { const result = await authorizeWithLdapCredentialsAsync(db, { name: "test", password: "test", - credentialType: "ldap", }); // Assert expect(result.name).toBe("test"); + expect(result.groups).toHaveLength(0); // Groups are needed in signIn events callback const dbUser = await db.query.users.findFirst({ where: and(eq(users.name, "test"), eq(users.provider, "ldap")), }); @@ -219,7 +215,8 @@ describe("authorizeWithLdapCredentials", () => { expect(credentialsUser?.id).not.toBe(result.id); }); - test("should authorize user with correct credentials and update name", async () => { + // The name update occurs in the signIn event callback + test("should authorize user with correct credentials and return updated name", async () => { // Arrange const spy = vi.spyOn(ldapClient, "LdapClient"); spy.mockImplementation( @@ -251,11 +248,10 @@ describe("authorizeWithLdapCredentials", () => { const result = await authorizeWithLdapCredentialsAsync(db, { name: "test", password: "test", - credentialType: "ldap", }); // Assert - expect(result).toEqual({ id: userId, name: "test" }); + expect(result).toEqual({ id: userId, name: "test", groups: [] }); const dbUser = await db.query.users.findFirst({ where: eq(users.id, userId), @@ -263,12 +259,12 @@ describe("authorizeWithLdapCredentials", () => { expect(dbUser).toBeDefined(); expect(dbUser?.id).toBe(userId); - expect(dbUser?.name).toBe("test"); + expect(dbUser?.name).toBe("test-old"); expect(dbUser?.email).toBe("test@gmail.com"); expect(dbUser?.provider).toBe("ldap"); }); - test("should authorize user with correct credentials and add him to the groups that he is in LDAP but not in Homar", async () => { + test("should authorize user with correct credentials and return his groups", async () => { // Arrange const spy = vi.spyOn(ldapClient, "LdapClient"); spy.mockImplementation( @@ -311,83 +307,9 @@ describe("authorizeWithLdapCredentials", () => { const result = await authorizeWithLdapCredentialsAsync(db, { name: "test", password: "test", - credentialType: "ldap", }); // Assert - expect(result).toEqual({ id: userId, name: "test" }); - - const dbGroupMembers = await db.query.groupMembers.findMany(); - expect(dbGroupMembers).toHaveLength(1); - }); - - test("should authorize user with correct credentials and remove him from groups he is in Homarr but not in LDAP", async () => { - // Arrange - const spy = vi.spyOn(ldapClient, "LdapClient"); - spy.mockImplementation( - () => - ({ - bindAsync: vi.fn(() => Promise.resolve()), - searchAsync: vi.fn((argument: { options: { filter: string } }) => - argument.options.filter.includes("group") - ? Promise.resolve([ - { - cn: "homarr_example", - }, - ]) - : Promise.resolve([ - { - dn: "test55", - mail: "test@gmail.com", - }, - ]), - ), - disconnectAsync: vi.fn(), - }) as unknown as ldapClient.LdapClient, - ); - const db = createDb(); - const userId = createId(); - await db.insert(users).values({ - id: userId, - name: "test", - email: "test@gmail.com", - provider: "ldap", - }); - - const groupIds = [createId(), createId()] as const; - await db.insert(groups).values([ - { - id: groupIds[0], - name: "homarr_example", - }, - { - id: groupIds[1], - name: "homarr_no_longer_member", - }, - ]); - await db.insert(groupMembers).values([ - { - userId, - groupId: groupIds[0], - }, - { - userId, - groupId: groupIds[1], - }, - ]); - - // Act - const result = await authorizeWithLdapCredentialsAsync(db, { - name: "test", - password: "test", - credentialType: "ldap", - }); - - // Assert - expect(result).toEqual({ id: userId, name: "test" }); - - const dbGroupMembers = await db.query.groupMembers.findMany(); - expect(dbGroupMembers).toHaveLength(1); - expect(dbGroupMembers[0]?.groupId).toBe(groupIds[0]); + expect(result).toEqual({ id: userId, name: "test", groups: ["homarr_example"] }); }); }); diff --git a/packages/auth/server.ts b/packages/auth/server.ts index 903902f62..913086ece 100644 --- a/packages/auth/server.ts +++ b/packages/auth/server.ts @@ -1,3 +1,4 @@ export { hasQueryAccessToIntegrationsAsync } from "./permissions/integration-query-permissions"; export { getIntegrationsWithPermissionsAsync } from "./permissions/integrations-with-permissions"; export { isProviderEnabled } from "./providers/check-provider"; +export { createSessionCallback, createSessionAsync } from "./callbacks"; diff --git a/packages/auth/session.ts b/packages/auth/session.ts index 5510ca89b..1bf9320d7 100644 --- a/packages/auth/session.ts +++ b/packages/auth/session.ts @@ -5,7 +5,8 @@ import type { Database } from "@homarr/db"; import { getCurrentUserPermissionsAsync } from "./callbacks"; -export const sessionTokenCookieName = "next-auth.session-token"; +// Default of authjs +export const sessionTokenCookieName = "authjs.session-token"; export const expireDateAfter = (seconds: number) => { return new Date(Date.now() + seconds * 1000); diff --git a/packages/auth/test/adapter.spec.ts b/packages/auth/test/adapter.spec.ts new file mode 100644 index 000000000..7c74ffa4e --- /dev/null +++ b/packages/auth/test/adapter.spec.ts @@ -0,0 +1,67 @@ +import { describe, expect, test } from "vitest"; + +import { users } from "@homarr/db/schema/sqlite"; +import { createDb } from "@homarr/db/test"; + +import { createAdapter } from "../adapter"; + +describe("createAdapter should create drizzle adapter", () => { + test.each([["credentials" as const], ["ldap" as const], ["oidc" as const]])( + "createAdapter getUserByEmail should return user for provider %s when this provider provided", + async (provider) => { + // Arrange + const db = createDb(); + const adapter = createAdapter(db, provider); + const email = "test@example.com"; + await db.insert(users).values({ id: "1", name: "test", email, provider }); + + // Act + const user = await adapter.getUserByEmail?.(email); + + // Assert + expect(user).toEqual({ + id: "1", + name: "test", + email, + emailVerified: null, + image: null, + }); + }, + ); + + test.each([ + ["credentials", ["ldap", "oidc"]], + ["ldap", ["credentials", "oidc"]], + ["oidc", ["credentials", "ldap"]], + ] as const)( + "createAdapter getUserByEmail should return null if only for other providers than %s exist", + async (requestedProvider, existingProviders) => { + // Arrange + const db = createDb(); + const adapter = createAdapter(db, requestedProvider); + const email = "test@example.com"; + for (const provider of existingProviders) { + await db.insert(users).values({ id: provider, name: `test-${provider}`, email, provider }); + } + + // Act + const user = await adapter.getUserByEmail?.(email); + + // Assert + expect(user).toBeNull(); + }, + ); + + test("createAdapter getUserByEmail should throw error if provider is unknown", async () => { + // Arrange + const db = createDb(); + const adapter = createAdapter(db, "unknown"); + const email = "test@example.com"; + + // Act + const actAsync = async () => await adapter.getUserByEmail?.(email); + + // Assert + await expect(actAsync()).rejects.toThrow("Unable to get user by email for unknown provider"); + }); +}); diff --git a/packages/auth/test/callbacks.spec.ts b/packages/auth/test/callbacks.spec.ts index 20dbf17f2..d2da21a3b 100644 --- a/packages/auth/test/callbacks.spec.ts +++ b/packages/auth/test/callbacks.spec.ts @@ -1,9 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies"; -import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; -import { cookies } from "next/headers"; -import type { Adapter, AdapterUser } from "@auth/core/adapters"; -import type { Account } from "next-auth"; +import type { AdapterUser } from "@auth/core/adapters"; import type { JWT } from "next-auth/jwt"; import { describe, expect, test, vi } from "vitest"; @@ -11,7 +7,15 @@ import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema import { createDb } from "@homarr/db/test"; import * as definitions from "@homarr/definitions"; -import { createSessionCallback, createSignInCallback, getCurrentUserPermissionsAsync } from "../callbacks"; +import { createSessionCallback, getCurrentUserPermissionsAsync } from "../callbacks"; + +// This one is placed here because it's used in multiple tests and needs to be the same reference +const setCookies = vi.fn(); +vi.mock("next/headers", () => ({ + cookies: () => ({ + set: setCookies, + }), +})); describe("getCurrentUserPermissions", () => { test("should return empty permissions when non existing user requested", async () => { @@ -135,167 +139,3 @@ describe("session callback", () => { expect(result.user!.name).toEqual(user.name); }); }); - -type AdapterSessionInput = Parameters>[0]; - -const createAdapter = () => { - const result = { - createSession: (input: AdapterSessionInput) => input, - }; - - vi.spyOn(result, "createSession"); - return result; -}; - -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -type SessionExport = typeof import("../session"); -const mockSessionToken = "e9ef3010-6981-4a81-b9d6-8495d09cf3b5"; -const mockSessionExpiry = new Date("2023-07-01"); -vi.mock("../env.mjs", () => { - return { - env: { - AUTH_SESSION_EXPIRY_TIME: 60 * 60 * 24 * 7, - }, - }; -}); -vi.mock("../session", async (importOriginal) => { - const mod = await importOriginal(); - - const generateSessionToken = (): typeof mockSessionToken => mockSessionToken; - const expireDateAfter = (_seconds: number) => mockSessionExpiry; - - return { - ...mod, - generateSessionToken, - expireDateAfter, - } satisfies SessionExport; -}); -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -type HeadersExport = typeof import("next/headers"); -vi.mock("next/headers", async (importOriginal) => { - const mod = await importOriginal(); - - const result = { - set: (name: string, value: string, options: Partial) => options as ResponseCookie, - } as unknown as ReadonlyRequestCookies; - - vi.spyOn(result, "set"); - - const cookies = () => result; - - return { ...mod, cookies } satisfies HeadersExport; -}); - -describe("createSignInCallback", () => { - test("should return true if not credentials request and set colorScheme & sessionToken cookie", async () => { - // Arrange - const isCredentialsRequest = false; - const db = await prepareDbForSigninAsync("1"); - const signInCallback = createSignInCallback(createAdapter(), db, isCredentialsRequest); - - // Act - const result = await signInCallback({ - user: { id: "1", emailVerified: new Date("2023-01-13") }, - account: {} as Account, - }); - - // Assert - expect(result).toBe(true); - }); - - test("should return false if no adapter.createSession", async () => { - // Arrange - const isCredentialsRequest = true; - const db = await prepareDbForSigninAsync("1"); - const signInCallback = createSignInCallback( - // https://github.com/nextauthjs/next-auth/issues/6106 - { createSession: undefined } as unknown as Adapter, - db, - isCredentialsRequest, - ); - - // Act - const result = await signInCallback({ - user: { id: "1", emailVerified: new Date("2023-01-13") }, - account: {} as Account, - }); - - // Assert - expect(result).toBe(false); - }); - - test("should call adapter.createSession with correct input", async () => { - // Arrange - const adapter = createAdapter(); - const isCredentialsRequest = true; - const db = await prepareDbForSigninAsync("1"); - const signInCallback = createSignInCallback(adapter, db, isCredentialsRequest); - const user = { id: "1", emailVerified: new Date("2023-01-13") }; - const account = {} as Account; - - // Act - await signInCallback({ user, account }); - - // Assert - expect(adapter.createSession).toHaveBeenCalledWith({ - sessionToken: mockSessionToken, - userId: user.id, - expires: mockSessionExpiry, - }); - expect(cookies().set).toHaveBeenCalledWith("next-auth.session-token", mockSessionToken, { - path: "/", - expires: mockSessionExpiry, - httpOnly: true, - sameSite: "lax", - secure: true, - }); - }); - - test("should set colorScheme from db as cookie", async () => { - // Arrange - const isCredentialsRequest = false; - const db = await prepareDbForSigninAsync("1"); - const signInCallback = createSignInCallback(createAdapter(), db, isCredentialsRequest); - - // Act - const result = await signInCallback({ - user: { id: "1", emailVerified: new Date("2023-01-13") }, - account: {} as Account, - }); - - // Assert - expect(result).toBe(true); - expect(cookies().set).toHaveBeenCalledWith( - "homarr-color-scheme", - "dark", - expect.objectContaining({ - path: "/", - }), - ); - }); - - test("should return false if user not found in db", async () => { - // Arrange - const isCredentialsRequest = true; - const db = await prepareDbForSigninAsync("other-id"); - const signInCallback = createSignInCallback(createAdapter(), db, isCredentialsRequest); - - // Act - const result = await signInCallback({ - user: { id: "1", emailVerified: new Date("2023-01-13") }, - account: {} as Account, - }); - - // Assert - expect(result).toBe(false); - }); -}); - -const prepareDbForSigninAsync = async (userId: string) => { - const db = createDb(); - await db.insert(users).values({ - id: userId, - colorScheme: "dark", - }); - return db; -}; diff --git a/packages/auth/test/events.spec.ts b/packages/auth/test/events.spec.ts new file mode 100644 index 000000000..02f44abd3 --- /dev/null +++ b/packages/auth/test/events.spec.ts @@ -0,0 +1,190 @@ +import type { ResponseCookie } from "next/dist/compiled/@edge-runtime/cookies"; +import type { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; +import { cookies } from "next/headers"; +import { describe, expect, test, vi } from "vitest"; + +import { eq } from "@homarr/db"; +import type { Database } from "@homarr/db"; +import { groupMembers, groups, users } from "@homarr/db/schema/sqlite"; +import { createDb } from "@homarr/db/test"; + +import { createSignInEventHandler } from "../events"; + +vi.mock("../env.mjs", () => { + return { + env: { + AUTH_OIDC_GROUPS_ATTRIBUTE: "someRandomGroupsKey", + }, + }; +}); +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +type HeadersExport = typeof import("next/headers"); +vi.mock("next/headers", async (importOriginal) => { + const mod = await importOriginal(); + + const result = { + set: (name: string, value: string, options: Partial) => options as ResponseCookie, + } as unknown as ReadonlyRequestCookies; + + vi.spyOn(result, "set"); + + const cookies = () => result; + + return { ...mod, cookies } satisfies HeadersExport; +}); + +describe("createSignInEventHandler should create signInEventHandler", () => { + describe("signInEventHandler should synchronize ldap groups", () => { + test("should add missing group membership", async () => { + // Arrange + const db = createDb(); + await createUserAsync(db); + await createGroupAsync(db); + const eventHandler = createSignInEventHandler(db); + + // Act + await eventHandler?.({ + user: { id: "1", name: "test", groups: ["test"] } as never, + profile: undefined, + account: null, + }); + + // Assert + const dbGroupMembers = await db.query.groupMembers.findFirst({ + where: eq(groupMembers.userId, "1"), + }); + expect(dbGroupMembers?.groupId).toBe("1"); + }); + test("should remove group membership", async () => { + // Arrange + const db = createDb(); + await createUserAsync(db); + await createGroupAsync(db); + await db.insert(groupMembers).values({ + userId: "1", + groupId: "1", + }); + const eventHandler = createSignInEventHandler(db); + + // Act + await eventHandler?.({ + user: { id: "1", name: "test", groups: [] } as never, + profile: undefined, + account: null, + }); + + // Assert + const dbGroupMembers = await db.query.groupMembers.findFirst({ + where: eq(groupMembers.userId, "1"), + }); + expect(dbGroupMembers).toBeUndefined(); + }); + }); + describe("signInEventHandler should synchronize oidc groups", () => { + test("should add missing group membership", async () => { + // Arrange + const db = createDb(); + await createUserAsync(db); + await createGroupAsync(db); + const eventHandler = createSignInEventHandler(db); + + // Act + await eventHandler?.({ + user: { id: "1", name: "test" }, + profile: { preferred_username: "test", someRandomGroupsKey: ["test"] }, + account: null, + }); + + // Assert + const dbGroupMembers = await db.query.groupMembers.findFirst({ + where: eq(groupMembers.userId, "1"), + }); + expect(dbGroupMembers?.groupId).toBe("1"); + }); + test("should remove group membership", async () => { + // Arrange + const db = createDb(); + await createUserAsync(db); + await createGroupAsync(db); + await db.insert(groupMembers).values({ + userId: "1", + groupId: "1", + }); + const eventHandler = createSignInEventHandler(db); + + // Act + await eventHandler?.({ + user: { id: "1", name: "test" }, + profile: { preferred_username: "test", someRandomGroupsKey: [] }, + account: null, + }); + + // Assert + const dbGroupMembers = await db.query.groupMembers.findFirst({ + where: eq(groupMembers.userId, "1"), + }); + expect(dbGroupMembers).toBeUndefined(); + }); + }); + test.each([ + ["ldap" as const, { name: "test-new" }, undefined], + ["oidc" as const, { name: "test" }, { preferred_username: "test-new" }], + ["oidc" as const, { name: "test" }, { preferred_username: "test@example.com", name: "test-new" }], + ])("signInEventHandler should update username for %s provider", async (_provider, user, profile) => { + // Arrange + const db = createDb(); + await createUserAsync(db); + const eventHandler = createSignInEventHandler(db); + + // Act + await eventHandler?.({ + user: { id: "1", ...user }, + profile, + account: null, + }); + + // Assert + const dbUser = await db.query.users.findFirst({ + where: eq(users.id, "1"), + columns: { + name: true, + }, + }); + expect(dbUser?.name).toBe("test-new"); + }); + test("signInEventHandler should set homarr-color-scheme cookie", async () => { + // Arrange + const db = createDb(); + await createUserAsync(db); + const eventHandler = createSignInEventHandler(db); + + // Act + await eventHandler?.({ + user: { id: "1", name: "test" }, + profile: undefined, + account: null, + }); + + // Assert + expect(cookies().set).toHaveBeenCalledWith( + "homarr-color-scheme", + "dark", + expect.objectContaining({ + path: "/", + }), + ); + }); +}); + +const createUserAsync = async (db: Database) => + await db.insert(users).values({ + id: "1", + name: "test", + colorScheme: "dark", + }); + +const createGroupAsync = async (db: Database) => + await db.insert(groups).values({ + id: "1", + name: "test", + }); diff --git a/packages/cli/package.json b/packages/cli/package.json index 504cdd556..d7766ea3a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,7 +33,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/common/package.json b/packages/common/package.json index 654d577ad..a7c915305 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -27,7 +27,7 @@ "dependencies": { "@homarr/log": "workspace:^0.1.0", "dayjs": "^1.11.13", - "next": "^14.2.14", + "next": "^14.2.15", "react": "^18.3.1", "tldts": "^6.1.50" }, @@ -35,7 +35,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/cron-job-runner/package.json b/packages/cron-job-runner/package.json index 80191dbd5..ef29c3968 100644 --- a/packages/cron-job-runner/package.json +++ b/packages/cron-job-runner/package.json @@ -30,7 +30,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/cron-job-status/package.json b/packages/cron-job-status/package.json index 2c8adc5ce..a5f88277b 100644 --- a/packages/cron-job-status/package.json +++ b/packages/cron-job-status/package.json @@ -29,7 +29,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/cron-jobs-core/package.json b/packages/cron-jobs-core/package.json index 16ba379bb..976e940f4 100644 --- a/packages/cron-jobs-core/package.json +++ b/packages/cron-jobs-core/package.json @@ -32,7 +32,7 @@ "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/node-cron": "^3.0.11", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/cron-jobs/package.json b/packages/cron-jobs/package.json index a63b4ccef..44617bc18 100644 --- a/packages/cron-jobs/package.json +++ b/packages/cron-jobs/package.json @@ -41,7 +41,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/db/migrations/mysql/0009_wakeful_tenebrous.sql b/packages/db/migrations/mysql/0009_wakeful_tenebrous.sql new file mode 100644 index 000000000..c910dd415 --- /dev/null +++ b/packages/db/migrations/mysql/0009_wakeful_tenebrous.sql @@ -0,0 +1,9 @@ +CREATE TABLE `apiKey` ( + `id` varchar(64) NOT NULL, + `apiKey` text NOT NULL, + `salt` text NOT NULL, + `userId` varchar(64) NOT NULL, + CONSTRAINT `apiKey_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +ALTER TABLE `apiKey` ADD CONSTRAINT `apiKey_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/packages/db/migrations/mysql/0010_melted_pestilence.sql b/packages/db/migrations/mysql/0010_melted_pestilence.sql new file mode 100644 index 000000000..569c6dbf2 --- /dev/null +++ b/packages/db/migrations/mysql/0010_melted_pestilence.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `firstDayOfWeek` tinyint DEFAULT 1 NOT NULL; \ No newline at end of file diff --git a/packages/db/migrations/mysql/meta/0009_snapshot.json b/packages/db/migrations/mysql/meta/0009_snapshot.json new file mode 100644 index 000000000..f5aeeac9a --- /dev/null +++ b/packages/db/migrations/mysql/meta/0009_snapshot.json @@ -0,0 +1,1481 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "a3bc0eee-b87c-4453-9087-99d18e9fe71e", + "prevId": "7063029b-fdf4-405f-9808-85c61747185b", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "apiKey": { + "name": "apiKey", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_userId_user_id_fk": { + "name": "apiKey_userId_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "apiKey_id": { + "name": "apiKey_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "app": { + "name": "app", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "app_id": { + "name": "app_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "boardGroupPermission": { + "name": "boardGroupPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardGroupPermission_board_id_board_id_fk": { + "name": "boardGroupPermission_board_id_board_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardGroupPermission_group_id_group_id_fk": { + "name": "boardGroupPermission_group_id_group_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardGroupPermission_board_id_group_id_permission_pk": { + "name": "boardGroupPermission_board_id_group_id_permission_pk", + "columns": ["board_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "boardUserPermission": { + "name": "boardUserPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardUserPermission_board_id_board_id_fk": { + "name": "boardUserPermission_board_id_board_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardUserPermission_user_id_user_id_fk": { + "name": "boardUserPermission_user_id_user_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardUserPermission_board_id_user_id_permission_pk": { + "name": "boardUserPermission_board_id_user_id_permission_pk", + "columns": ["board_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "board": { + "name": "board", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('fixed')" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('no-repeat')" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('cover')" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('#fa5252')" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('#fd7e14')" + }, + "opacity": { + "name": "opacity", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + } + }, + "indexes": {}, + "foreignKeys": { + "board_creator_id_user_id_fk": { + "name": "board_creator_id_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "board_id": { + "name": "board_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "board_name_unique": { + "name": "board_name_unique", + "columns": ["name"] + } + } + }, + "groupMember": { + "name": "groupMember", + "columns": { + "groupId": { + "name": "groupId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_groupId_group_id_fk": { + "name": "groupMember_groupId_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_userId_user_id_fk": { + "name": "groupMember_userId_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_groupId_userId_pk": { + "name": "groupMember_groupId_userId_pk", + "columns": ["groupId", "userId"] + } + }, + "uniqueConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "groupId": { + "name": "groupId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_groupId_group_id_fk": { + "name": "groupPermission_groupId_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_id_user_id_fk": { + "name": "group_owner_id_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "group_id": { + "name": "group_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "iconRepository_id": { + "name": "iconRepository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_slug": { + "name": "iconRepository_slug", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "iconRepository_iconRepository_id": { + "name": "iconRepository_iconRepository_id", + "columns": ["iconRepository_id"] + } + }, + "uniqueConstraints": {} + }, + "icon": { + "name": "icon", + "columns": { + "icon_id": { + "name": "icon_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "varchar(250)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_checksum": { + "name": "icon_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_id": { + "name": "iconRepository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_iconRepository_id_iconRepository_iconRepository_id_fk": { + "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["iconRepository_id"], + "columnsTo": ["iconRepository_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "icon_icon_id": { + "name": "icon_icon_id", + "columns": ["icon_id"] + } + }, + "uniqueConstraints": {} + }, + "integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_group_permission__pk": { + "name": "integration_group_permission__pk", + "columns": ["integration_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "integration_item": { + "name": "integration_item", + "columns": { + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_item_item_id_item_id_fk": { + "name": "integration_item_item_id_item_id_fk", + "tableFrom": "integration_item", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_item_integration_id_integration_id_fk": { + "name": "integration_item_integration_id_integration_id_fk", + "tableFrom": "integration_item", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_item_item_id_integration_id_pk": { + "name": "integration_item_item_id_integration_id_pk", + "columns": ["item_id", "integration_id"] + } + }, + "uniqueConstraints": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": ["kind"], + "isUnique": false + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "integrationSecret_integration_id_integration_id_fk": { + "name": "integrationSecret_integration_id_integration_id_fk", + "tableFrom": "integrationSecret", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationSecret_integration_id_kind_pk": { + "name": "integrationSecret_integration_id_kind_pk", + "columns": ["integration_id", "kind"] + } + }, + "uniqueConstraints": {} + }, + "integrationUserPermission": { + "name": "integrationUserPermission", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "name": "integrationUserPermission_integration_id_user_id_permission_pk", + "columns": ["integration_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "integration_id": { + "name": "integration_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiration_date": { + "name": "expiration_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invite_creator_id_user_id_fk": { + "name": "invite_creator_id_user_id_fk", + "tableFrom": "invite", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "invite_id": { + "name": "invite_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "invite_token_unique": { + "name": "invite_token_unique", + "columns": ["token"] + } + } + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "indexes": {}, + "foreignKeys": { + "item_section_id_section_id_fk": { + "name": "item_section_id_section_id_fk", + "tableFrom": "item", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_id": { + "name": "item_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "search_engine": { + "name": "search_engine", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "short": { + "name": "short", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "search_engine_id": { + "name": "search_engine_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "section": { + "name": "section", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_board_id_board_id_fk": { + "name": "section_board_id_board_id_fk", + "tableFrom": "section", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_parent_section_id_section_id_fk": { + "name": "section_parent_section_id_section_id_fk", + "tableFrom": "section", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_id": { + "name": "section_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "key": { + "name": "key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "serverSetting_key": { + "name": "serverSetting_key", + "columns": ["key"] + } + }, + "uniqueConstraints": { + "serverSetting_key_unique": { + "name": "serverSetting_key_unique", + "columns": ["key"] + } + } + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_sessionToken": { + "name": "session_sessionToken", + "columns": ["sessionToken"] + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credentials'" + }, + "homeBoardId": { + "name": "homeBoardId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "colorScheme": { + "name": "colorScheme", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_homeBoardId_board_id_fk": { + "name": "user_homeBoardId_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["homeBoardId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_id": { + "name": "user_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {} + } + }, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/db/migrations/mysql/meta/0010_snapshot.json b/packages/db/migrations/mysql/meta/0010_snapshot.json new file mode 100644 index 000000000..f2bc5ff0e --- /dev/null +++ b/packages/db/migrations/mysql/meta/0010_snapshot.json @@ -0,0 +1,1489 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "ea24896b-e311-49b3-855b-e50b224e7719", + "prevId": "a3bc0eee-b87c-4453-9087-99d18e9fe71e", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "apiKey": { + "name": "apiKey", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_userId_user_id_fk": { + "name": "apiKey_userId_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "apiKey_id": { + "name": "apiKey_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "app": { + "name": "app", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "app_id": { + "name": "app_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "boardGroupPermission": { + "name": "boardGroupPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardGroupPermission_board_id_board_id_fk": { + "name": "boardGroupPermission_board_id_board_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardGroupPermission_group_id_group_id_fk": { + "name": "boardGroupPermission_group_id_group_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardGroupPermission_board_id_group_id_permission_pk": { + "name": "boardGroupPermission_board_id_group_id_permission_pk", + "columns": ["board_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "boardUserPermission": { + "name": "boardUserPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardUserPermission_board_id_board_id_fk": { + "name": "boardUserPermission_board_id_board_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardUserPermission_user_id_user_id_fk": { + "name": "boardUserPermission_user_id_user_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardUserPermission_board_id_user_id_permission_pk": { + "name": "boardUserPermission_board_id_user_id_permission_pk", + "columns": ["board_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "board": { + "name": "board", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('fixed')" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('no-repeat')" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('cover')" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('#fa5252')" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('#fd7e14')" + }, + "opacity": { + "name": "opacity", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + } + }, + "indexes": {}, + "foreignKeys": { + "board_creator_id_user_id_fk": { + "name": "board_creator_id_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "board_id": { + "name": "board_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "board_name_unique": { + "name": "board_name_unique", + "columns": ["name"] + } + } + }, + "groupMember": { + "name": "groupMember", + "columns": { + "groupId": { + "name": "groupId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_groupId_group_id_fk": { + "name": "groupMember_groupId_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_userId_user_id_fk": { + "name": "groupMember_userId_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_groupId_userId_pk": { + "name": "groupMember_groupId_userId_pk", + "columns": ["groupId", "userId"] + } + }, + "uniqueConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "groupId": { + "name": "groupId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_groupId_group_id_fk": { + "name": "groupPermission_groupId_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_id_user_id_fk": { + "name": "group_owner_id_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "group_id": { + "name": "group_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "iconRepository_id": { + "name": "iconRepository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_slug": { + "name": "iconRepository_slug", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "iconRepository_iconRepository_id": { + "name": "iconRepository_iconRepository_id", + "columns": ["iconRepository_id"] + } + }, + "uniqueConstraints": {} + }, + "icon": { + "name": "icon", + "columns": { + "icon_id": { + "name": "icon_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "varchar(250)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_checksum": { + "name": "icon_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_id": { + "name": "iconRepository_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_iconRepository_id_iconRepository_iconRepository_id_fk": { + "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["iconRepository_id"], + "columnsTo": ["iconRepository_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "icon_icon_id": { + "name": "icon_icon_id", + "columns": ["icon_id"] + } + }, + "uniqueConstraints": {} + }, + "integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_group_permission__pk": { + "name": "integration_group_permission__pk", + "columns": ["integration_id", "group_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "integration_item": { + "name": "integration_item", + "columns": { + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_item_item_id_item_id_fk": { + "name": "integration_item_item_id_item_id_fk", + "tableFrom": "integration_item", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_item_integration_id_integration_id_fk": { + "name": "integration_item_integration_id_integration_id_fk", + "tableFrom": "integration_item", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_item_item_id_integration_id_pk": { + "name": "integration_item_item_id_integration_id_pk", + "columns": ["item_id", "integration_id"] + } + }, + "uniqueConstraints": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": ["kind"], + "isUnique": false + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "integrationSecret_integration_id_integration_id_fk": { + "name": "integrationSecret_integration_id_integration_id_fk", + "tableFrom": "integrationSecret", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationSecret_integration_id_kind_pk": { + "name": "integrationSecret_integration_id_kind_pk", + "columns": ["integration_id", "kind"] + } + }, + "uniqueConstraints": {} + }, + "integrationUserPermission": { + "name": "integrationUserPermission", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "name": "integrationUserPermission_integration_id_user_id_permission_pk", + "columns": ["integration_id", "user_id", "permission"] + } + }, + "uniqueConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "integration_id": { + "name": "integration_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiration_date": { + "name": "expiration_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invite_creator_id_user_id_fk": { + "name": "invite_creator_id_user_id_fk", + "tableFrom": "invite", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "invite_id": { + "name": "invite_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "invite_token_unique": { + "name": "invite_token_unique", + "columns": ["token"] + } + } + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "indexes": {}, + "foreignKeys": { + "item_section_id_section_id_fk": { + "name": "item_section_id_section_id_fk", + "tableFrom": "item", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "item_id": { + "name": "item_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "search_engine": { + "name": "search_engine", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "short": { + "name": "short", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "search_engine_id": { + "name": "search_engine_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "section": { + "name": "section", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_board_id_board_id_fk": { + "name": "section_board_id_board_id_fk", + "tableFrom": "section", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_parent_section_id_section_id_fk": { + "name": "section_parent_section_id_section_id_fk", + "tableFrom": "section", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "section_id": { + "name": "section_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "key": { + "name": "key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('{\"json\": {}}')" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "serverSetting_key": { + "name": "serverSetting_key", + "columns": ["key"] + } + }, + "uniqueConstraints": { + "serverSetting_key_unique": { + "name": "serverSetting_key_unique", + "columns": ["key"] + } + } + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "session_sessionToken": { + "name": "session_sessionToken", + "columns": ["sessionToken"] + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credentials'" + }, + "homeBoardId": { + "name": "homeBoardId", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "colorScheme": { + "name": "colorScheme", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "firstDayOfWeek": { + "name": "firstDayOfWeek", + "type": "tinyint", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": { + "user_homeBoardId_board_id_fk": { + "name": "user_homeBoardId_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["homeBoardId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_id": { + "name": "user_id", + "columns": ["id"] + } + }, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": ["identifier", "token"] + } + }, + "uniqueConstraints": {} + } + }, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/packages/db/migrations/mysql/meta/_journal.json b/packages/db/migrations/mysql/meta/_journal.json index 59bfad2ae..dacc6be5a 100644 --- a/packages/db/migrations/mysql/meta/_journal.json +++ b/packages/db/migrations/mysql/meta/_journal.json @@ -64,6 +64,20 @@ "when": 1727532165317, "tag": "0008_far_lifeguard", "breakpoints": true + }, + { + "idx": 9, + "version": "5", + "when": 1728074730696, + "tag": "0009_wakeful_tenebrous", + "breakpoints": true + }, + { + "idx": 10, + "version": "5", + "when": 1728142597094, + "tag": "0010_melted_pestilence", + "breakpoints": true } ] } diff --git a/packages/db/migrations/sqlite/0009_stale_roulette.sql b/packages/db/migrations/sqlite/0009_stale_roulette.sql new file mode 100644 index 000000000..31d201b3c --- /dev/null +++ b/packages/db/migrations/sqlite/0009_stale_roulette.sql @@ -0,0 +1,7 @@ +CREATE TABLE `apiKey` ( + `id` text PRIMARY KEY NOT NULL, + `apiKey` text NOT NULL, + `salt` text NOT NULL, + `userId` text NOT NULL, + FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); diff --git a/packages/db/migrations/sqlite/0010_gorgeous_stingray.sql b/packages/db/migrations/sqlite/0010_gorgeous_stingray.sql new file mode 100644 index 000000000..550ac5f9f --- /dev/null +++ b/packages/db/migrations/sqlite/0010_gorgeous_stingray.sql @@ -0,0 +1 @@ +ALTER TABLE `user` ADD `firstDayOfWeek` integer DEFAULT 1 NOT NULL; \ No newline at end of file diff --git a/packages/db/migrations/sqlite/meta/0009_snapshot.json b/packages/db/migrations/sqlite/meta/0009_snapshot.json new file mode 100644 index 000000000..d4eb69560 --- /dev/null +++ b/packages/db/migrations/sqlite/meta/0009_snapshot.json @@ -0,0 +1,1414 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "698b643b-d741-4c0a-9ab1-c5978cc2c950", + "prevId": "752c40b4-2326-4bb0-a714-42dc54a2b3e0", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {} + }, + "apiKey": { + "name": "apiKey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_userId_user_id_fk": { + "name": "apiKey_userId_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "app": { + "name": "app", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "boardGroupPermission": { + "name": "boardGroupPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardGroupPermission_board_id_board_id_fk": { + "name": "boardGroupPermission_board_id_board_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardGroupPermission_group_id_group_id_fk": { + "name": "boardGroupPermission_group_id_group_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardGroupPermission_board_id_group_id_permission_pk": { + "columns": ["board_id", "group_id", "permission"], + "name": "boardGroupPermission_board_id_group_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "boardUserPermission": { + "name": "boardUserPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardUserPermission_board_id_board_id_fk": { + "name": "boardUserPermission_board_id_board_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardUserPermission_user_id_user_id_fk": { + "name": "boardUserPermission_user_id_user_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardUserPermission_board_id_user_id_permission_pk": { + "columns": ["board_id", "user_id", "permission"], + "name": "boardUserPermission_board_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "board": { + "name": "board", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'no-repeat'" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cover'" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#fa5252'" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#fd7e14'" + }, + "opacity": { + "name": "opacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + } + }, + "indexes": { + "board_name_unique": { + "name": "board_name_unique", + "columns": ["name"], + "isUnique": true + } + }, + "foreignKeys": { + "board_creator_id_user_id_fk": { + "name": "board_creator_id_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "groupMember": { + "name": "groupMember", + "columns": { + "groupId": { + "name": "groupId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_groupId_group_id_fk": { + "name": "groupMember_groupId_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_userId_user_id_fk": { + "name": "groupMember_userId_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_groupId_userId_pk": { + "columns": ["groupId", "userId"], + "name": "groupMember_groupId_userId_pk" + } + }, + "uniqueConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "groupId": { + "name": "groupId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_groupId_group_id_fk": { + "name": "groupPermission_groupId_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_id_user_id_fk": { + "name": "group_owner_id_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "iconRepository_id": { + "name": "iconRepository_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "iconRepository_slug": { + "name": "iconRepository_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "icon": { + "name": "icon", + "columns": { + "icon_id": { + "name": "icon_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_checksum": { + "name": "icon_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_id": { + "name": "iconRepository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_iconRepository_id_iconRepository_iconRepository_id_fk": { + "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["iconRepository_id"], + "columnsTo": ["iconRepository_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationGroupPermissions_integration_id_group_id_permission_pk": { + "columns": ["integration_id", "group_id", "permission"], + "name": "integrationGroupPermissions_integration_id_group_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "integration_item": { + "name": "integration_item", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_item_item_id_item_id_fk": { + "name": "integration_item_item_id_item_id_fk", + "tableFrom": "integration_item", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_item_integration_id_integration_id_fk": { + "name": "integration_item_integration_id_integration_id_fk", + "tableFrom": "integration_item", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_item_item_id_integration_id_pk": { + "columns": ["item_id", "integration_id"], + "name": "integration_item_item_id_integration_id_pk" + } + }, + "uniqueConstraints": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": ["kind"], + "isUnique": false + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "integrationSecret_integration_id_integration_id_fk": { + "name": "integrationSecret_integration_id_integration_id_fk", + "tableFrom": "integrationSecret", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationSecret_integration_id_kind_pk": { + "columns": ["integration_id", "kind"], + "name": "integrationSecret_integration_id_kind_pk" + } + }, + "uniqueConstraints": {} + }, + "integrationUserPermission": { + "name": "integrationUserPermission", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "columns": ["integration_id", "user_id", "permission"], + "name": "integrationUserPermission_integration_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiration_date": { + "name": "expiration_date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invite_token_unique": { + "name": "invite_token_unique", + "columns": ["token"], + "isUnique": true + } + }, + "foreignKeys": { + "invite_creator_id_user_id_fk": { + "name": "invite_creator_id_user_id_fk", + "tableFrom": "invite", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": {}, + "foreignKeys": { + "item_section_id_section_id_fk": { + "name": "item_section_id_section_id_fk", + "tableFrom": "item", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "search_engine": { + "name": "search_engine", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "short": { + "name": "short", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "section": { + "name": "section", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_board_id_board_id_fk": { + "name": "section_board_id_board_id_fk", + "tableFrom": "section", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_parent_section_id_section_id_fk": { + "name": "section_parent_section_id_section_id_fk", + "tableFrom": "section", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": { + "serverSetting_key_unique": { + "name": "serverSetting_key_unique", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credentials'" + }, + "homeBoardId": { + "name": "homeBoardId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "colorScheme": { + "name": "colorScheme", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_homeBoardId_board_id_fk": { + "name": "user_homeBoardId_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["homeBoardId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/db/migrations/sqlite/meta/0010_snapshot.json b/packages/db/migrations/sqlite/meta/0010_snapshot.json new file mode 100644 index 000000000..753417fef --- /dev/null +++ b/packages/db/migrations/sqlite/meta/0010_snapshot.json @@ -0,0 +1,1422 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "52be1539-1b06-4581-8347-ce2102e782c7", + "prevId": "698b643b-d741-4c0a-9ab1-c5978cc2c950", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "userId_idx": { + "name": "userId_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": ["provider", "providerAccountId"], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {} + }, + "apiKey": { + "name": "apiKey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "apiKey": { + "name": "apiKey", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_userId_user_id_fk": { + "name": "apiKey_userId_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "app": { + "name": "app", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "href": { + "name": "href", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "boardGroupPermission": { + "name": "boardGroupPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardGroupPermission_board_id_board_id_fk": { + "name": "boardGroupPermission_board_id_board_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardGroupPermission_group_id_group_id_fk": { + "name": "boardGroupPermission_group_id_group_id_fk", + "tableFrom": "boardGroupPermission", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardGroupPermission_board_id_group_id_permission_pk": { + "columns": ["board_id", "group_id", "permission"], + "name": "boardGroupPermission_board_id_group_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "boardUserPermission": { + "name": "boardUserPermission", + "columns": { + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "boardUserPermission_board_id_board_id_fk": { + "name": "boardUserPermission_board_id_board_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "boardUserPermission_user_id_user_id_fk": { + "name": "boardUserPermission_user_id_user_id_fk", + "tableFrom": "boardUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "boardUserPermission_board_id_user_id_permission_pk": { + "columns": ["board_id", "user_id", "permission"], + "name": "boardUserPermission_board_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "board": { + "name": "board", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_title": { + "name": "page_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "meta_title": { + "name": "meta_title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_image_url": { + "name": "logo_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "favicon_image_url": { + "name": "favicon_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_url": { + "name": "background_image_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "background_image_attachment": { + "name": "background_image_attachment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fixed'" + }, + "background_image_repeat": { + "name": "background_image_repeat", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'no-repeat'" + }, + "background_image_size": { + "name": "background_image_size", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cover'" + }, + "primary_color": { + "name": "primary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#fa5252'" + }, + "secondary_color": { + "name": "secondary_color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'#fd7e14'" + }, + "opacity": { + "name": "opacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 100 + }, + "custom_css": { + "name": "custom_css", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "column_count": { + "name": "column_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 10 + } + }, + "indexes": { + "board_name_unique": { + "name": "board_name_unique", + "columns": ["name"], + "isUnique": true + } + }, + "foreignKeys": { + "board_creator_id_user_id_fk": { + "name": "board_creator_id_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "groupMember": { + "name": "groupMember", + "columns": { + "groupId": { + "name": "groupId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupMember_groupId_group_id_fk": { + "name": "groupMember_groupId_group_id_fk", + "tableFrom": "groupMember", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "groupMember_userId_user_id_fk": { + "name": "groupMember_userId_user_id_fk", + "tableFrom": "groupMember", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "groupMember_groupId_userId_pk": { + "columns": ["groupId", "userId"], + "name": "groupMember_groupId_userId_pk" + } + }, + "uniqueConstraints": {} + }, + "groupPermission": { + "name": "groupPermission", + "columns": { + "groupId": { + "name": "groupId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groupPermission_groupId_group_id_fk": { + "name": "groupPermission_groupId_group_id_fk", + "tableFrom": "groupPermission", + "tableTo": "group", + "columnsFrom": ["groupId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_id_user_id_fk": { + "name": "group_owner_id_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "iconRepository": { + "name": "iconRepository", + "columns": { + "iconRepository_id": { + "name": "iconRepository_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "iconRepository_slug": { + "name": "iconRepository_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "icon": { + "name": "icon", + "columns": { + "icon_id": { + "name": "icon_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "icon_name": { + "name": "icon_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon_checksum": { + "name": "icon_checksum", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "iconRepository_id": { + "name": "iconRepository_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "icon_iconRepository_id_iconRepository_iconRepository_id_fk": { + "name": "icon_iconRepository_id_iconRepository_iconRepository_id_fk", + "tableFrom": "icon", + "tableTo": "iconRepository", + "columnsFrom": ["iconRepository_id"], + "columnsTo": ["iconRepository_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "integrationGroupPermissions": { + "name": "integrationGroupPermissions", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationGroupPermissions_integration_id_integration_id_fk": { + "name": "integrationGroupPermissions_integration_id_integration_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationGroupPermissions_group_id_group_id_fk": { + "name": "integrationGroupPermissions_group_id_group_id_fk", + "tableFrom": "integrationGroupPermissions", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationGroupPermissions_integration_id_group_id_permission_pk": { + "columns": ["integration_id", "group_id", "permission"], + "name": "integrationGroupPermissions_integration_id_group_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "integration_item": { + "name": "integration_item", + "columns": { + "item_id": { + "name": "item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_item_item_id_item_id_fk": { + "name": "integration_item_item_id_item_id_fk", + "tableFrom": "integration_item", + "tableTo": "item", + "columnsFrom": ["item_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_item_integration_id_integration_id_fk": { + "name": "integration_item_integration_id_integration_id_fk", + "tableFrom": "integration_item", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_item_item_id_integration_id_pk": { + "columns": ["item_id", "integration_id"], + "name": "integration_item_item_id_integration_id_pk" + } + }, + "uniqueConstraints": {} + }, + "integrationSecret": { + "name": "integrationSecret", + "columns": { + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_secret__kind_idx": { + "name": "integration_secret__kind_idx", + "columns": ["kind"], + "isUnique": false + }, + "integration_secret__updated_at_idx": { + "name": "integration_secret__updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + } + }, + "foreignKeys": { + "integrationSecret_integration_id_integration_id_fk": { + "name": "integrationSecret_integration_id_integration_id_fk", + "tableFrom": "integrationSecret", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationSecret_integration_id_kind_pk": { + "columns": ["integration_id", "kind"], + "name": "integrationSecret_integration_id_kind_pk" + } + }, + "uniqueConstraints": {} + }, + "integrationUserPermission": { + "name": "integrationUserPermission", + "columns": { + "integration_id": { + "name": "integration_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrationUserPermission_integration_id_integration_id_fk": { + "name": "integrationUserPermission_integration_id_integration_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "integration", + "columnsFrom": ["integration_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integrationUserPermission_user_id_user_id_fk": { + "name": "integrationUserPermission_user_id_user_id_fk", + "tableFrom": "integrationUserPermission", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integrationUserPermission_integration_id_user_id_permission_pk": { + "columns": ["integration_id", "user_id", "permission"], + "name": "integrationUserPermission_integration_id_user_id_permission_pk" + } + }, + "uniqueConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration__kind_idx": { + "name": "integration__kind_idx", + "columns": ["kind"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expiration_date": { + "name": "expiration_date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invite_token_unique": { + "name": "invite_token_unique", + "columns": ["token"], + "isUnique": true + } + }, + "foreignKeys": { + "invite_creator_id_user_id_fk": { + "name": "invite_creator_id_user_id_fk", + "tableFrom": "invite", + "tableTo": "user", + "columnsFrom": ["creator_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "item": { + "name": "item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "options": { + "name": "options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + }, + "advanced_options": { + "name": "advanced_options", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": {}, + "foreignKeys": { + "item_section_id_section_id_fk": { + "name": "item_section_id_section_id_fk", + "tableFrom": "item", + "tableTo": "section", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "search_engine": { + "name": "search_engine", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "short": { + "name": "short", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "url_template": { + "name": "url_template", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "section": { + "name": "section", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "board_id": { + "name": "board_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "x_offset": { + "name": "x_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "y_offset": { + "name": "y_offset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_section_id": { + "name": "parent_section_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "section_board_id_board_id_fk": { + "name": "section_board_id_board_id_fk", + "tableFrom": "section", + "tableTo": "board", + "columnsFrom": ["board_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "section_parent_section_id_section_id_fk": { + "name": "section_parent_section_id_section_id_fk", + "tableFrom": "section", + "tableTo": "section", + "columnsFrom": ["parent_section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "serverSetting": { + "name": "serverSetting", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"json\": {}}'" + } + }, + "indexes": { + "serverSetting_key_unique": { + "name": "serverSetting_key_unique", + "columns": ["key"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": ["userId"], + "isUnique": false + } + }, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "salt": { + "name": "salt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'credentials'" + }, + "homeBoardId": { + "name": "homeBoardId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "colorScheme": { + "name": "colorScheme", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "firstDayOfWeek": { + "name": "firstDayOfWeek", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": { + "user_homeBoardId_board_id_fk": { + "name": "user_homeBoardId_board_id_fk", + "tableFrom": "user", + "tableTo": "board", + "columnsFrom": ["homeBoardId"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "columns": ["identifier", "token"], + "name": "verificationToken_identifier_token_pk" + } + }, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/db/migrations/sqlite/meta/_journal.json b/packages/db/migrations/sqlite/meta/_journal.json index edfca232c..40d9bf4d0 100644 --- a/packages/db/migrations/sqlite/meta/_journal.json +++ b/packages/db/migrations/sqlite/meta/_journal.json @@ -64,6 +64,20 @@ "when": 1727526190343, "tag": "0008_third_thor", "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1728074724956, + "tag": "0009_stale_roulette", + "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1728142590232, + "tag": "0010_gorgeous_stingray", + "breakpoints": true } ] } diff --git a/packages/db/package.json b/packages/db/package.json index 23a27b732..67ffa31ba 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -31,7 +31,7 @@ }, "prettier": "@homarr/prettier-config", "dependencies": { - "@auth/core": "^0.35.3", + "@auth/core": "^0.37.0", "@homarr/common": "workspace:^0.1.0", "@homarr/definitions": "workspace:^0.1.0", "@homarr/log": "workspace:^0.1.0", @@ -39,8 +39,8 @@ "@testcontainers/mysql": "^10.13.2", "better-sqlite3": "^11.3.0", "dotenv": "^16.4.5", - "drizzle-kit": "^0.24.2", - "drizzle-orm": "^0.33.0", + "drizzle-kit": "^0.25.0", + "drizzle-orm": "^0.34.1", "mysql2": "3.11.3" }, "devDependencies": { @@ -49,8 +49,8 @@ "@homarr/tsconfig": "workspace:^0.1.0", "@types/better-sqlite3": "7.6.11", "dotenv-cli": "^7.4.2", - "eslint": "^9.11.1", + "eslint": "^9.12.0", "prettier": "^3.3.3", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index 64fe30cc0..383ac2e5a 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -1,7 +1,8 @@ import type { AdapterAccount } from "@auth/core/adapters"; +import type { DayOfWeek } from "@mantine/dates"; import { relations } from "drizzle-orm"; import type { AnyMySqlColumn } from "drizzle-orm/mysql-core"; -import { boolean, index, int, mysqlTable, primaryKey, text, timestamp, varchar } from "drizzle-orm/mysql-core"; +import { boolean, index, int, mysqlTable, primaryKey, text, timestamp, tinyint, varchar } from "drizzle-orm/mysql-core"; import type { BackgroundImageAttachment, @@ -19,6 +20,17 @@ import type { } from "@homarr/definitions"; import { backgroundImageAttachments, backgroundImageRepeats, backgroundImageSizes } from "@homarr/definitions"; +export const apiKeys = mysqlTable("apiKey", { + id: varchar("id", { length: 64 }).notNull().primaryKey(), + apiKey: text("apiKey").notNull(), + salt: text("salt").notNull(), + userId: varchar("userId", { length: 64 }) + .notNull() + .references((): AnyMySqlColumn => users.id, { + onDelete: "cascade", + }), +}); + export const users = mysqlTable("user", { id: varchar("id", { length: 64 }).notNull().primaryKey(), name: text("name"), @@ -32,6 +44,7 @@ export const users = mysqlTable("user", { onDelete: "set null", }), colorScheme: varchar("colorScheme", { length: 5 }).$type().default("auto").notNull(), + firstDayOfWeek: tinyint("firstDayOfWeek").$type().default(1).notNull(), // Defaults to Monday }); export const accounts = mysqlTable( @@ -341,6 +354,13 @@ export const serverSettings = mysqlTable("serverSetting", { value: text("value").default('{"json": {}}').notNull(), // empty superjson object }); +export const apiKeyRelations = relations(apiKeys, ({ one }) => ({ + user: one(users, { + fields: [apiKeys.userId], + references: [users.id], + }), +})); + export const searchEngines = mysqlTable("search_engine", { id: varchar("id", { length: 64 }).notNull().primaryKey(), iconUrl: text("icon_url").notNull(), diff --git a/packages/db/schema/sqlite.ts b/packages/db/schema/sqlite.ts index eebd75114..cec5844c1 100644 --- a/packages/db/schema/sqlite.ts +++ b/packages/db/schema/sqlite.ts @@ -1,4 +1,5 @@ import type { AdapterAccount } from "@auth/core/adapters"; +import type { DayOfWeek } from "@mantine/dates"; import type { InferSelectModel } from "drizzle-orm"; import { relations } from "drizzle-orm"; import type { AnySQLiteColumn } from "drizzle-orm/sqlite-core"; @@ -20,6 +21,17 @@ import type { WidgetKind, } from "@homarr/definitions"; +export const apiKeys = sqliteTable("apiKey", { + id: text("id").notNull().primaryKey(), + apiKey: text("apiKey").notNull(), + salt: text("salt").notNull(), + userId: text("userId") + .notNull() + .references((): AnySQLiteColumn => users.id, { + onDelete: "cascade", + }), +}); + export const users = sqliteTable("user", { id: text("id").notNull().primaryKey(), name: text("name"), @@ -33,6 +45,7 @@ export const users = sqliteTable("user", { onDelete: "set null", }), colorScheme: text("colorScheme").$type().default("auto").notNull(), + firstDayOfWeek: int("firstDayOfWeek").$type().default(1).notNull(), // Defaults to Monday }); export const accounts = sqliteTable( @@ -343,6 +356,13 @@ export const serverSettings = sqliteTable("serverSetting", { value: text("value").default('{"json": {}}').notNull(), // empty superjson object }); +export const apiKeyRelations = relations(apiKeys, ({ one }) => ({ + user: one(users, { + fields: [apiKeys.userId], + references: [users.id], + }), +})); + export const searchEngines = sqliteTable("search_engine", { id: text("id").notNull().primaryKey(), iconUrl: text("icon_url").notNull(), diff --git a/packages/definitions/package.json b/packages/definitions/package.json index 9227b5c20..7585d0718 100644 --- a/packages/definitions/package.json +++ b/packages/definitions/package.json @@ -28,7 +28,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/form/package.json b/packages/form/package.json index 1db3d65b2..1871c1cfe 100644 --- a/packages/form/package.json +++ b/packages/form/package.json @@ -30,7 +30,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/icons/package.json b/packages/icons/package.json index a140b5fe9..406684bfe 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -29,7 +29,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/integrations/package.json b/packages/integrations/package.json index f30cf0b3d..de9f5dbda 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -39,7 +39,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/log/package.json b/packages/log/package.json index 06c891da6..e87d4f31a 100644 --- a/packages/log/package.json +++ b/packages/log/package.json @@ -28,13 +28,13 @@ "dependencies": { "ioredis": "5.4.1", "superjson": "2.2.1", - "winston": "3.14.2" + "winston": "3.15.0" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/modals-collection/package.json b/packages/modals-collection/package.json index 6ff6a7078..72d2def67 100644 --- a/packages/modals-collection/package.json +++ b/packages/modals-collection/package.json @@ -33,15 +33,15 @@ "@mantine/core": "^7.13.2", "@tabler/icons-react": "^3.19.0", "dayjs": "^1.11.13", - "next": "^14.2.14", + "next": "^14.2.15", "react": "^18.3.1" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" }, "prettier": "@homarr/prettier-config" } diff --git a/packages/modals/package.json b/packages/modals/package.json index b08df6729..2e714ecc3 100644 --- a/packages/modals/package.json +++ b/packages/modals/package.json @@ -32,7 +32,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/notifications/package.json b/packages/notifications/package.json index d77538189..4384d745b 100644 --- a/packages/notifications/package.json +++ b/packages/notifications/package.json @@ -31,7 +31,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/old-import/package.json b/packages/old-import/package.json index baf02811a..34f8b2545 100644 --- a/packages/old-import/package.json +++ b/packages/old-import/package.json @@ -33,8 +33,8 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" }, "prettier": "@homarr/prettier-config" } diff --git a/packages/old-schema/package.json b/packages/old-schema/package.json index f4aef0427..e721b6e98 100644 --- a/packages/old-schema/package.json +++ b/packages/old-schema/package.json @@ -27,8 +27,8 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" }, "prettier": "@homarr/prettier-config" } diff --git a/packages/ping/package.json b/packages/ping/package.json index d4f10ef9f..da7bf402a 100644 --- a/packages/ping/package.json +++ b/packages/ping/package.json @@ -29,7 +29,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/redis/package.json b/packages/redis/package.json index e578e0862..faa399085 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -33,7 +33,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/server-settings/package.json b/packages/server-settings/package.json index aeb05eb77..a852efca1 100644 --- a/packages/server-settings/package.json +++ b/packages/server-settings/package.json @@ -25,7 +25,7 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/spotlight/package.json b/packages/spotlight/package.json index 5d9840b32..b23509a1b 100644 --- a/packages/spotlight/package.json +++ b/packages/spotlight/package.json @@ -35,7 +35,7 @@ "@mantine/spotlight": "^7.13.2", "@tabler/icons-react": "^3.19.0", "jotai": "^2.10.0", - "next": "^14.2.14", + "next": "^14.2.15", "react": "^18.3.1", "use-deep-compare-effect": "^1.8.1" }, @@ -43,8 +43,8 @@ "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" }, "prettier": "@homarr/prettier-config" } diff --git a/packages/spotlight/src/components/spotlight.tsx b/packages/spotlight/src/components/spotlight.tsx index d10d4d69c..f84af4add 100644 --- a/packages/spotlight/src/components/spotlight.tsx +++ b/packages/spotlight/src/components/spotlight.tsx @@ -51,10 +51,7 @@ export const Spotlight = () => { store={spotlightStore} > ((value) => z.number().min(0).max(6).safeParse(value).success), +}); + export const userSchemas = { signIn: signInSchema, registration: registrationSchema, @@ -116,4 +120,5 @@ export const userSchemas = { changeHomeBoard: changeHomeBoardSchema, changePasswordApi: changePasswordApiSchema, changeColorScheme: changeColorSchemeSchema, + firstDayOfWeek: firstDayOfWeekSchema, }; diff --git a/packages/widgets/package.json b/packages/widgets/package.json index a03442a81..74ee3276a 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -59,16 +59,16 @@ "clsx": "^2.1.1", "dayjs": "^1.11.13", "mantine-react-table": "2.0.0-beta.6", - "next": "^14.2.14", + "next": "^14.2.15", "react": "^18.3.1", - "video.js": "^8.17.4" + "video.js": "^8.18.1" }, "devDependencies": { "@homarr/eslint-config": "workspace:^0.2.0", "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", "@types/video.js": "^7.3.58", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" } } diff --git a/packages/widgets/src/app/component.tsx b/packages/widgets/src/app/component.tsx index c8628030a..eec922902 100644 --- a/packages/widgets/src/app/component.tsx +++ b/packages/widgets/src/app/component.tsx @@ -59,11 +59,7 @@ export default function AppWidget({ options, isEditMode }: WidgetComponentProps< {options.pingEnabled && app.href ? ( - - } - > + }> ) : null} diff --git a/packages/widgets/src/calendar/component.tsx b/packages/widgets/src/calendar/component.tsx index 02d19fd3f..1e21122e9 100644 --- a/packages/widgets/src/calendar/component.tsx +++ b/packages/widgets/src/calendar/component.tsx @@ -5,6 +5,8 @@ import { useParams } from "next/navigation"; import { Calendar } from "@mantine/dates"; import dayjs from "dayjs"; +import { clientApi } from "@homarr/api/client"; + import type { WidgetComponentProps } from "../definition"; import { CalendarDay } from "./calender-day"; import classes from "./component.module.css"; @@ -13,6 +15,7 @@ export default function CalendarWidget({ isEditMode, serverData }: WidgetCompone const [month, setMonth] = useState(new Date()); const params = useParams(); const locale = params.locale as string; + const [firstDayOfWeek] = clientApi.user.getFirstDayOfWeekForUserOrDefault.useSuspenseQuery(); return ( - t("common.rtl", { - value: formatNumber( - data.reduce((count, { adsBlockedTodayPercentage }) => count + adsBlockedTodayPercentage, 0), - 2, - ), - symbol: "%", - }), + value: (data) => + `${formatNumber( + data.reduce((count, { adsBlockedTodayPercentage }) => count + adsBlockedTodayPercentage, 0), + 2, + )}%`, label: (t) => t("widget.dnsHoleSummary.data.adsBlockedTodayPercentage"), color: "rgba(255, 165, 20, 0.4)", // YELLOW }, @@ -135,7 +132,7 @@ const stats = [ interface StatItem { icon: TablerIcon; - value: (x: DnsHoleSummary[], t: TranslationFunction) => string; + value: (x: DnsHoleSummary[]) => string; label: stringOrTranslation; color: string; } @@ -184,14 +181,14 @@ const StatCard = ({ item, data, usePiHoleColors, t }: StatCardProps) => { gap="1cqmin" > - {item.value(data, t)} + {item.value(data)} {item.label && ( diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index 982f55109..7fafc932a 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -641,8 +641,6 @@ export default function DownloadClientsWidget({ }, }); - const isLangRtl = tCommon("rtl", { value: "0", symbol: "1" }).startsWith("1"); - //Used for Global Torrent Ratio const globalTraffic = clients .filter(({ integration: { kind } }) => @@ -676,13 +674,12 @@ export default function DownloadClientsWidget({ px="var(--space-size)" justify={integrationTypes.includes("torrent") ? "space-between" : "end"} style={{ - flexDirection: isLangRtl ? "row-reverse" : "row", borderTop: "0.0625rem solid var(--border-color)", }} > {integrationTypes.includes("torrent") && ( - - {tCommon("rtl", { value: t("globalRatio"), symbol: tCommon("symbols.colon") })} + + {`${t("globalRatio")}:`} {(globalTraffic.up / globalTraffic.down).toFixed(2)} )} @@ -758,21 +755,10 @@ const NormalizedLine = ({ values?: number | string | string[]; }) => { const t = useScopedI18n("widget.downloads.items"); - const tCommon = useScopedI18n("common"); - const translatedKey = t(`${itemKey}.detailsTitle`); - const isLangRtl = tCommon("rtl", { value: "0", symbol: "1" }).startsWith("1"); //Maybe make a common "isLangRtl" somewhere - const keyString = tCommon("rtl", { value: translatedKey, symbol: tCommon("symbols.colon") }); if (typeof values !== "number" && (values === undefined || values.length === 0)) return null; return ( - - {keyString} + + {`${t(`${itemKey}.detailsTitle`)}:`} {Array.isArray(values) ? ( {values.map((value) => ( diff --git a/packages/widgets/src/media-requests/stats/component.tsx b/packages/widgets/src/media-requests/stats/component.tsx index a38a73853..75296856a 100644 --- a/packages/widgets/src/media-requests/stats/component.tsx +++ b/packages/widgets/src/media-requests/stats/component.tsx @@ -31,7 +31,6 @@ export default function MediaServerWidget({ itemId, }: WidgetComponentProps<"mediaRequests-requestStats">) { const t = useScopedI18n("widget.mediaRequests-requestStats"); - const tCommon = useScopedI18n("common"); const isQueryEnabled = Boolean(itemId); const { data: requestStats, isError: _isError } = clientApi.widget.mediaRequests.getStats.useQuery( { @@ -188,8 +187,7 @@ export default function MediaServerWidget({ {user.displayName} - {tCommon("rtl", { value: t("titles.users.requests"), symbol: tCommon("symbols.colon") }) + - user.requestCount} + {`${t("titles.users.requests")}: ${user.requestCount}`} diff --git a/packages/widgets/src/notebook/default-content.ts b/packages/widgets/src/notebook/default-content.ts index da525c48d..ff2abd1f4 100644 --- a/packages/widgets/src/notebook/default-content.ts +++ b/packages/widgets/src/notebook/default-content.ts @@ -1,6 +1,6 @@ export const defaultContent = `

- +

Welcome to Homarr's notebook widget

diff --git a/packages/widgets/src/weather/icon.tsx b/packages/widgets/src/weather/icon.tsx index 25a25b34d..fa44f3d26 100644 --- a/packages/widgets/src/weather/icon.tsx +++ b/packages/widgets/src/weather/icon.tsx @@ -62,18 +62,8 @@ export const WeatherDescription = ({ weatherOnly, time, weatherCode, maxTemp, mi {dayjs(time).format("dddd MMMM D YYYY")} {t(`kind.${name}`)} - - {tCommon("rtl", { - value: tCommon("information.max"), - symbol: tCommon("symbols.colon"), - }) + maxTemp} - - - {tCommon("rtl", { - value: tCommon("information.min"), - symbol: tCommon("symbols.colon"), - }) + minTemp} - + {`${tCommon("information.max")}: ${maxTemp}`} + {`${tCommon("information.min")}: ${minTemp}`} ); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb3425e52..a96e948d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,10 +18,10 @@ importers: version: link:tooling/prettier '@turbo/gen': specifier: ^2.1.3 - version: 2.1.3(@types/node@20.16.10)(typescript@5.6.2) + version: 2.1.3(@types/node@20.16.11)(typescript@5.6.3) '@vitejs/plugin-react': specifier: ^4.3.2 - version: 4.3.2(vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) + version: 4.3.2(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) '@vitest/coverage-v8': specifier: ^2.1.2 version: 2.1.2(vitest@2.1.2) @@ -44,14 +44,14 @@ importers: specifier: ^2.1.3 version: 2.1.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 vite-tsconfig-paths: specifier: ^5.0.1 - version: 5.0.1(typescript@5.6.2)(vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) + version: 5.0.1(typescript@5.6.3)(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) vitest: specifier: ^2.1.2 - version: 2.1.2(@types/node@20.16.10)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + version: 2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) apps/nextjs: dependencies: @@ -137,35 +137,35 @@ importers: specifier: ^7.13.2 version: 7.13.2(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(@tiptap/extension-link@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0))(@tiptap/react@2.8.0(@tiptap/core@2.8.0(@tiptap/pm@2.8.0))(@tiptap/pm@2.8.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@million/lint': - specifier: 1.0.8 - version: 1.0.8(rollup@4.21.3)(webpack-sources@3.2.3) + specifier: 1.0.9 + version: 1.0.9(rollup@4.21.3)(webpack-sources@3.2.3) '@t3-oss/env-nextjs': specifier: ^0.11.1 - version: 0.11.1(typescript@5.6.2)(zod@3.23.8) + version: 0.11.1(typescript@5.6.3)(zod@3.23.8) '@tabler/icons-react': specifier: ^3.19.0 version: 3.19.0(react@18.3.1) '@tanstack/react-query': - specifier: ^5.59.0 - version: 5.59.0(react@18.3.1) + specifier: ^5.59.9 + version: 5.59.9(react@18.3.1) '@tanstack/react-query-devtools': - specifier: ^5.59.0 - version: 5.59.0(@tanstack/react-query@5.59.0(react@18.3.1))(react@18.3.1) + specifier: ^5.59.9 + version: 5.59.9(@tanstack/react-query@5.59.9(react@18.3.1))(react@18.3.1) '@tanstack/react-query-next-experimental': - specifier: 5.59.0 - version: 5.59.0(@tanstack/react-query@5.59.0(react@18.3.1))(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) + specifier: 5.59.9 + version: 5.59.9(@tanstack/react-query@5.59.9(react@18.3.1))(next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5))(react@18.3.1) '@trpc/client': specifier: next - version: 11.0.0-rc.553(@trpc/server@11.0.0-rc.553) + version: 11.0.0-rc.569(@trpc/server@11.0.0-rc.569) '@trpc/next': specifier: next - version: 11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/react-query@11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@trpc/server@11.0.0-rc.553)(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/react-query@11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@trpc/server@11.0.0-rc.569)(next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@trpc/react-query': specifier: next - version: 11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@trpc/server': specifier: next - version: 11.0.0-rc.553 + version: 11.0.0-rc.569 '@xterm/addon-canvas': specifier: ^0.7.0 version: 0.7.0(@xterm/xterm@5.5.0) @@ -200,8 +200,8 @@ importers: specifier: 2.0.0-beta.6 version: 2.0.0-beta.6(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/dates@7.13.2(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(@tabler/icons-react@3.19.0(react@18.3.1))(clsx@2.1.1)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) postcss-preset-mantine: specifier: ^1.17.0 version: 1.17.0(postcss@8.4.47) @@ -221,8 +221,8 @@ importers: specifier: ^0.14.1 version: 0.14.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) sass: - specifier: ^1.79.4 - version: 1.79.4 + specifier: ^1.79.5 + version: 1.79.5 superjson: specifier: 2.2.1 version: 2.2.1 @@ -246,8 +246,8 @@ importers: specifier: 2.4.4 version: 2.4.4 '@types/node': - specifier: ^20.16.10 - version: 20.16.10 + specifier: ^20.16.11 + version: 20.16.11 '@types/prismjs': specifier: ^1.26.4 version: 1.26.4 @@ -255,8 +255,8 @@ importers: specifier: ^18.3.11 version: 18.3.11 '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.0 + specifier: ^18.3.1 + version: 18.3.1 '@types/swagger-ui-react': specifier: ^4.18.3 version: 4.18.3 @@ -264,8 +264,8 @@ importers: specifier: ^9.0.1 version: 9.0.1 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 node-loader: specifier: ^2.0.0 version: 2.0.0(webpack@5.94.0) @@ -273,8 +273,8 @@ importers: specifier: ^3.3.3 version: 3.3.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 apps/tasks: dependencies: @@ -333,8 +333,8 @@ importers: specifier: 2.2.1 version: 2.2.1 undici: - specifier: 6.19.8 - version: 6.19.8 + specifier: 6.20.0 + version: 6.20.0 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -346,14 +346,14 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript '@types/node': - specifier: ^20.16.10 - version: 20.16.10 + specifier: ^20.16.11 + version: 20.16.11 dotenv-cli: specifier: ^7.4.2 version: 7.4.2 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 prettier: specifier: ^3.3.3 version: 3.3.3 @@ -361,8 +361,8 @@ importers: specifier: 4.13.3 version: 4.13.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 apps/websocket: dependencies: @@ -413,14 +413,14 @@ importers: specifier: ^8.5.12 version: 8.5.12 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 prettier: specifier: ^3.3.3 version: 3.3.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/analytics: dependencies: @@ -450,11 +450,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/api: dependencies: @@ -505,19 +505,19 @@ importers: version: link:../validation '@trpc/client': specifier: next - version: 11.0.0-rc.553(@trpc/server@11.0.0-rc.553) + version: 11.0.0-rc.569(@trpc/server@11.0.0-rc.569) '@trpc/react-query': specifier: next - version: 11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@trpc/server': specifier: next - version: 11.0.0-rc.553 + version: 11.0.0-rc.569 dockerode: specifier: ^4.0.2 version: 4.0.2 next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: specifier: ^18.3.1 version: 18.3.1 @@ -526,7 +526,7 @@ importers: version: 2.2.1 trpc-swagger: specifier: ^1.2.6 - version: 1.2.6(patch_hash=6s72z7zx33c52iesv5sewipn6i)(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(zod@3.23.8) + version: 1.2.6(patch_hash=6s72z7zx33c52iesv5sewipn6i)(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(zod@3.23.8) devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -541,23 +541,23 @@ importers: specifier: ^3.3.31 version: 3.3.31 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 prettier: specifier: ^3.3.3 version: 3.3.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/auth: dependencies: '@auth/core': - specifier: ^0.35.3 - version: 0.35.3 + specifier: ^0.37.0 + version: 0.37.0 '@auth/drizzle-adapter': - specifier: ^1.5.3 - version: 1.5.3 + specifier: ^1.7.0 + version: 1.7.0 '@homarr/common': specifier: workspace:^0.1.0 version: link:../common @@ -575,7 +575,7 @@ importers: version: link:../validation '@t3-oss/env-nextjs': specifier: ^0.11.1 - version: 0.11.1(typescript@5.6.2)(zod@3.23.8) + version: 0.11.1(typescript@5.6.3)(zod@3.23.8) bcrypt: specifier: ^5.1.1 version: 5.1.1 @@ -586,11 +586,11 @@ importers: specifier: 7.2.1 version: 7.2.1 next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) next-auth: specifier: 5.0.0-beta.22 - version: 5.0.0-beta.22(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1) + version: 5.0.0-beta.22(next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -614,14 +614,14 @@ importers: specifier: 0.9.0 version: 0.9.0 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 prettier: specifier: ^3.3.3 version: 3.3.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/cli: dependencies: @@ -651,11 +651,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/common: dependencies: @@ -666,8 +666,8 @@ importers: specifier: ^1.11.13 version: 1.11.13 next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: specifier: ^18.3.1 version: 18.3.1 @@ -685,11 +685,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/cron-job-runner: dependencies: @@ -713,11 +713,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/cron-job-status: dependencies: @@ -735,11 +735,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/cron-jobs: dependencies: @@ -796,11 +796,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/cron-jobs-core: dependencies: @@ -824,17 +824,17 @@ importers: specifier: ^3.0.11 version: 3.0.11 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/db: dependencies: '@auth/core': - specifier: ^0.35.3 - version: 0.35.3 + specifier: ^0.37.0 + version: 0.37.0 '@homarr/common': specifier: workspace:^0.1.0 version: link:../common @@ -857,11 +857,11 @@ importers: specifier: ^16.4.5 version: 16.4.5 drizzle-kit: - specifier: ^0.24.2 - version: 0.24.2 + specifier: ^0.25.0 + version: 0.25.0 drizzle-orm: - specifier: ^0.33.0 - version: 0.33.0(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@11.3.0)(mysql2@3.11.3)(react@18.3.1) + specifier: ^0.34.1 + version: 0.34.1(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@11.3.0)(mysql2@3.11.3)(react@18.3.1) mysql2: specifier: 3.11.3 version: 3.11.3 @@ -882,14 +882,14 @@ importers: specifier: ^7.4.2 version: 7.4.2 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 prettier: specifier: ^3.3.3 version: 3.3.3 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/definitions: dependencies: @@ -907,11 +907,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/form: dependencies: @@ -935,11 +935,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/icons: dependencies: @@ -960,11 +960,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/integrations: dependencies: @@ -1009,11 +1009,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/log: dependencies: @@ -1024,8 +1024,8 @@ importers: specifier: 2.2.1 version: 2.2.1 winston: - specifier: 3.14.2 - version: 3.14.2 + specifier: 3.15.0 + version: 3.15.0 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1037,11 +1037,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/modals: dependencies: @@ -1071,11 +1071,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/modals-collection: dependencies: @@ -1116,8 +1116,8 @@ importers: specifier: ^1.11.13 version: 1.11.13 next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: specifier: ^18.3.1 version: 18.3.1 @@ -1132,11 +1132,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/notifications: dependencies: @@ -1160,11 +1160,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/old-import: dependencies: @@ -1200,11 +1200,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/old-schema: dependencies: @@ -1222,11 +1222,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/ping: dependencies: @@ -1247,11 +1247,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/redis: dependencies: @@ -1284,11 +1284,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/server-settings: devDependencies: @@ -1302,11 +1302,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/spotlight: dependencies: @@ -1350,8 +1350,8 @@ importers: specifier: ^2.10.0 version: 2.10.0(@types/react@18.3.11)(react@18.3.1) next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: specifier: ^18.3.1 version: 18.3.1 @@ -1369,11 +1369,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/translation: dependencies: @@ -1397,11 +1397,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/ui: dependencies: @@ -1436,8 +1436,8 @@ importers: specifier: 2.0.0-beta.6 version: 2.0.0-beta.6(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/dates@7.13.2(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(@tabler/icons-react@3.19.0(react@18.3.1))(clsx@2.1.1)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: specifier: ^18.3.1 version: 18.3.1 @@ -1455,11 +1455,11 @@ importers: specifier: ^1.0.5 version: 1.0.5 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/validation: dependencies: @@ -1489,11 +1489,11 @@ importers: specifier: workspace:^0.1.0 version: link:../../tooling/typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 packages/widgets: dependencies: @@ -1606,14 +1606,14 @@ importers: specifier: 2.0.0-beta.6 version: 2.0.0-beta.6(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/dates@7.13.2(@mantine/core@7.13.2(@mantine/hooks@7.13.2(react@18.3.1))(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.13.2(react@18.3.1))(@tabler/icons-react@3.19.0(react@18.3.1))(clsx@2.1.1)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next: - specifier: ^14.2.14 - version: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + specifier: ^14.2.15 + version: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: specifier: ^18.3.1 version: 18.3.1 video.js: - specifier: ^8.17.4 - version: 8.17.4 + specifier: ^8.18.1 + version: 8.18.1 devDependencies: '@homarr/eslint-config': specifier: workspace:^0.2.0 @@ -1628,38 +1628,38 @@ importers: specifier: ^7.3.58 version: 7.3.58 eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 tooling/eslint: dependencies: '@next/eslint-plugin-next': - specifier: ^14.2.14 - version: 14.2.14 + specifier: ^14.2.15 + version: 14.2.15 eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@9.11.1) + version: 9.1.0(eslint@9.12.0) eslint-config-turbo: specifier: ^2.1.3 - version: 2.1.3(eslint@9.11.1) + version: 2.1.3(eslint@9.12.0) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint@9.11.1) + version: 2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint@9.12.0) eslint-plugin-jsx-a11y: specifier: ^6.10.0 - version: 6.10.0(eslint@9.11.1) + version: 6.10.0(eslint@9.12.0) eslint-plugin-react: specifier: ^7.37.1 - version: 7.37.1(eslint@9.11.1) + version: 7.37.1(eslint@9.12.0) eslint-plugin-react-hooks: specifier: ^4.6.2 - version: 4.6.2(eslint@9.11.1) + version: 4.6.2(eslint@9.12.0) typescript-eslint: - specifier: ^8.8.0 - version: 8.8.0(eslint@9.11.1)(typescript@5.6.2) + specifier: ^8.8.1 + version: 8.8.1(eslint@9.12.0)(typescript@5.6.3) devDependencies: '@homarr/prettier-config': specifier: workspace:^0.1.0 @@ -1668,11 +1668,11 @@ importers: specifier: workspace:^0.1.0 version: link:../typescript eslint: - specifier: ^9.11.1 - version: 9.11.1 + specifier: ^9.12.0 + version: 9.12.0 typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 tooling/github: {} @@ -1689,8 +1689,8 @@ importers: specifier: workspace:^0.1.0 version: link:../typescript typescript: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.3 + version: 5.6.3 tooling/semver: {} @@ -1720,8 +1720,22 @@ packages: nodemailer: optional: true - '@auth/drizzle-adapter@1.5.3': - resolution: {integrity: sha512-VNyYb1hiGtorJhCjShtncjN3TKXxtwxOwphYecq8lZSVuFDLIWHhp4ZbdZDjnmkvEk8G66IpFrYW84qpt+WUIg==} + '@auth/core@0.37.0': + resolution: {integrity: sha512-LybAgfFC5dta3Mu3al0UbnzMGVBpZRqLMvvXupQOfETtPNlL7rXgTO13EVRTCdvPqMQrVYjODUDvgVfQM1M3Qg==} + peerDependencies: + '@simplewebauthn/browser': ^9.0.1 + '@simplewebauthn/server': ^9.0.2 + nodemailer: ^6.8.0 + peerDependenciesMeta: + '@simplewebauthn/browser': + optional: true + '@simplewebauthn/server': + optional: true + nodemailer: + optional: true + + '@auth/drizzle-adapter@1.7.0': + resolution: {integrity: sha512-hijoZnkCf9UxiTiNgm0rw3t2JhzFyJSFRGugv9kT9zgdlUtVy7TzyW8/6Me3TPNuLXCzXW1PkPIc3zqZ/1JLdA==} '@axiomhq/js@1.0.0-rc.3': resolution: {integrity: sha512-Zm10TczcMLounWqC42nMkXQ7XKLqjzLrd5ia022oBKDUZqAFVg2y9d1quQVNV4FlXyg9MKDdfMjpKQRmzEGaog==} @@ -1883,9 +1897,11 @@ packages: '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild-kit/esm-loader@2.6.5': resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' '@esbuild/aix-ppc64@0.19.12': resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} @@ -2455,8 +2471,8 @@ packages: resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.11.1': - resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} + '@eslint/js@9.12.0': + resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': @@ -2508,12 +2524,20 @@ packages: peerDependencies: hono: ^4 + '@humanfs/core@0.19.0': + resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.5': + resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.0': - resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} '@ianvs/prettier-plugin-sort-imports@4.3.1': @@ -2639,70 +2663,70 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@million/install@1.0.8': - resolution: {integrity: sha512-v3hAMjE1BvPh4UMRTpwx2okj4XOsNTOIvX3MEyscP5mLKXwNiBjhS+aiHFhNU8BSTRBhlIrrX3sebrSVx/AywA==} + '@million/install@1.0.9': + resolution: {integrity: sha512-tJk7azrjsIkHl/xTppFw8K7M/CIrqa1y9J75By+dtW6jZBQi51DQlsp5J1SuAcTR89XwpGEuhN29HOvHosblPw==} hasBin: true - '@million/lint@1.0.8': - resolution: {integrity: sha512-DrdqkNdKamxb3YjOcDeVe1tt6SWpxVvbw9y9LxeIvJVOP4YvFHg2ZzfEX5o0we/xFDTip4D05YWvmAZl76gPhQ==} + '@million/lint@1.0.9': + resolution: {integrity: sha512-sKqdKUb0zfF2DUXAWuhbxIm+0j7CqG+689azpcSVEdW3zn9ezj3AoL7ne229Q9ElJAmr8Z0zCM73tH5QtyfXzA==} hasBin: true - '@next/env@14.2.14': - resolution: {integrity: sha512-/0hWQfiaD5//LvGNgc8PjvyqV50vGK0cADYzaoOOGN8fxzBn3iAiaq3S0tCRnFBldq0LVveLcxCTi41ZoYgAgg==} + '@next/env@14.2.15': + resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} - '@next/eslint-plugin-next@14.2.14': - resolution: {integrity: sha512-kV+OsZ56xhj0rnTn6HegyTGkoa16Mxjrpk7pjWumyB2P8JVQb8S9qtkjy/ye0GnTr4JWtWG4x/2qN40lKZ3iVQ==} + '@next/eslint-plugin-next@14.2.15': + resolution: {integrity: sha512-pKU0iqKRBlFB/ocOI1Ip2CkKePZpYpnw5bEItEkuZ/Nr9FQP1+p7VDWr4VfOdff4i9bFmrOaeaU1bFEyAcxiMQ==} - '@next/swc-darwin-arm64@14.2.14': - resolution: {integrity: sha512-bsxbSAUodM1cjYeA4o6y7sp9wslvwjSkWw57t8DtC8Zig8aG8V6r+Yc05/9mDzLKcybb6EN85k1rJDnMKBd9Gw==} + '@next/swc-darwin-arm64@14.2.15': + resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.14': - resolution: {integrity: sha512-cC9/I+0+SK5L1k9J8CInahduTVWGMXhQoXFeNvF0uNs3Bt1Ub0Azb8JzTU9vNCr0hnaMqiWu/Z0S1hfKc3+dww==} + '@next/swc-darwin-x64@14.2.15': + resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.14': - resolution: {integrity: sha512-RMLOdA2NU4O7w1PQ3Z9ft3PxD6Htl4uB2TJpocm+4jcllHySPkFaUIFacQ3Jekcg6w+LBaFvjSPthZHiPmiAUg==} + '@next/swc-linux-arm64-gnu@14.2.15': + resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.14': - resolution: {integrity: sha512-WgLOA4hT9EIP7jhlkPnvz49iSOMdZgDJVvbpb8WWzJv5wBD07M2wdJXLkDYIpZmCFfo/wPqFsFR4JS4V9KkQ2A==} + '@next/swc-linux-arm64-musl@14.2.15': + resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.14': - resolution: {integrity: sha512-lbn7svjUps1kmCettV/R9oAvEW+eUI0lo0LJNFOXoQM5NGNxloAyFRNByYeZKL3+1bF5YE0h0irIJfzXBq9Y6w==} + '@next/swc-linux-x64-gnu@14.2.15': + resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.14': - resolution: {integrity: sha512-7TcQCvLQ/hKfQRgjxMN4TZ2BRB0P7HwrGAYL+p+m3u3XcKTraUFerVbV3jkNZNwDeQDa8zdxkKkw2els/S5onQ==} + '@next/swc-linux-x64-musl@14.2.15': + resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.14': - resolution: {integrity: sha512-8i0Ou5XjTLEje0oj0JiI0Xo9L/93ghFtAUYZ24jARSeTMXLUx8yFIdhS55mTExq5Tj4/dC2fJuaT4e3ySvXU1A==} + '@next/swc-win32-arm64-msvc@14.2.15': + resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.14': - resolution: {integrity: sha512-2u2XcSaDEOj+96eXpyjHjtVPLhkAFw2nlaz83EPeuK4obF+HmtDJHqgR1dZB7Gb6V/d55FL26/lYVd0TwMgcOQ==} + '@next/swc-win32-ia32-msvc@14.2.15': + resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.14': - resolution: {integrity: sha512-MZom+OvZ1NZxuRovKt1ApevjiUJTcU2PmdJKL66xUPaJeRywnbGGRWUlaAOwunD6dX+pm83vj979NTC8QXjGWg==} + '@next/swc-win32-x64-msvc@14.2.15': + resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2729,6 +2753,82 @@ packages: '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@parcel/watcher-android-arm64@2.4.1': + resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.4.1': + resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.4.1': + resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.4.1': + resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.4.1': + resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.4.1': + resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.4.1': + resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.4.1': + resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.4.1': + resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.4.1': + resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.4.1': + resolution: {integrity: sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.4.1': + resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==} + engines: {node: '>= 10.0.0'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2972,27 +3072,27 @@ packages: resolution: {integrity: sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==} engines: {node: '>=12'} - '@tanstack/query-core@5.59.0': - resolution: {integrity: sha512-WGD8uIhX6/deH/tkZqPNcRyAhDUqs729bWKoByYHSogcshXfFbppOdTER5+qY7mFvu8KEFJwT0nxr8RfPTVh0Q==} + '@tanstack/query-core@5.59.9': + resolution: {integrity: sha512-vFGnblfJOKlOPyTR5M0ohWKb/03eGubh5KuGyzsDfc7VQ6F0nsB75kQIoLpwp3Wfj6fKv0wGoTUX8BsIfhxDfw==} '@tanstack/query-devtools@5.58.0': resolution: {integrity: sha512-iFdQEFXaYYxqgrv63ots+65FGI+tNp5ZS5PdMU1DWisxk3fez5HG3FyVlbUva+RdYS5hSLbxZ9aw3yEs97GNTw==} - '@tanstack/react-query-devtools@5.59.0': - resolution: {integrity: sha512-Kz7577FQGU8qmJxROIT/aOwmkTcxfBqgTP6r1AIvuJxVMVHPkp8eQxWQ7BnfBsy/KTJHiV9vMtRVo1+R1tB3vg==} + '@tanstack/react-query-devtools@5.59.9': + resolution: {integrity: sha512-Vfr8JPgx4GxopQOqdQTC7MAUbX1vuEqeexCIX0RiwjUmNCoHKUg2Mh3rTZPsx8Y7wscc7eWkBjiz03Dt/YM3oQ==} peerDependencies: - '@tanstack/react-query': ^5.59.0 + '@tanstack/react-query': ^5.59.9 react: ^18 || ^19 - '@tanstack/react-query-next-experimental@5.59.0': - resolution: {integrity: sha512-LrU1P7bi2qZWhuS0z+bxd0e1HqVuyiY6c5yebJoRum6WIq3QrFTUi9LW6swF4VMlJFqXf4Fk7nRlB7NFmBFhTA==} + '@tanstack/react-query-next-experimental@5.59.9': + resolution: {integrity: sha512-YEXROxfYiY6yd6FkMjHnLXqixCMrZ0+AmlT56H/oIlCEaqD5JpdlSUzD42cbq/l90eQw15SgC4/f8VVw2Cr8Rw==} peerDependencies: - '@tanstack/react-query': ^5.59.0 + '@tanstack/react-query': ^5.59.9 next: ^13 || ^14 || ^15 react: ^18 || ^19 - '@tanstack/react-query@5.59.0': - resolution: {integrity: sha512-YDXp3OORbYR+8HNQx+lf4F73NoiCmCcSvZvgxE29OifmQFk0sBlO26NWLHpcNERo92tVk3w+JQ53/vkcRUY1hA==} + '@tanstack/react-query@5.59.9': + resolution: {integrity: sha512-g2cbiw/ZIIrnUaQqhGtarTAsuLdKDNLtY5HNfRHVWY9kHDj96M4qs4ygJxHc119tPQpzZe4i9W7d2Gc2Gvng2A==} peerDependencies: react: ^18 || ^19 @@ -3221,18 +3321,18 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@trpc/client@11.0.0-rc.553': - resolution: {integrity: sha512-ICgziilbuAslRgHIVJZ162KaHQRpHXA1C1LPRsNpxPa9aStPF7eThAjn4V3JmDyJsuDLQgAmqLVRImlLPMYR5Q==} + '@trpc/client@11.0.0-rc.569': + resolution: {integrity: sha512-crS1mJdJVKfqtzCJUbrurooxrZdToAHMZuPw9SAYU+dDdiIqlzCovWatv0MK+UD7Nkezs8/jZC4C/wcstA5Mxg==} peerDependencies: - '@trpc/server': 11.0.0-rc.553+9b629cc67 + '@trpc/server': 11.0.0-rc.569+8049266de - '@trpc/next@11.0.0-rc.553': - resolution: {integrity: sha512-1bIuG4nKYmTuVFqgsqSbOGgIz2LsUcb1wXrK1zlfikRQHhssSMmpXSLSANfZffs/6lbFLVs057XaS9LX5HmCsw==} + '@trpc/next@11.0.0-rc.569': + resolution: {integrity: sha512-8O9IAWGWXnnyPVFRjEOoTuGtyIUJf25v/TqKoinywTgiLs4LDYwRY+p2RzWK2BXgFIoEucsoMLgSvhj4V/5SdQ==} peerDependencies: '@tanstack/react-query': ^5.54.1 - '@trpc/client': 11.0.0-rc.553+9b629cc67 - '@trpc/react-query': 11.0.0-rc.553+9b629cc67 - '@trpc/server': 11.0.0-rc.553+9b629cc67 + '@trpc/client': 11.0.0-rc.569+8049266de + '@trpc/react-query': 11.0.0-rc.569+8049266de + '@trpc/server': 11.0.0-rc.569+8049266de next: '*' react: '>=16.8.0' react-dom: '>=16.8.0' @@ -3242,17 +3342,17 @@ packages: '@trpc/react-query': optional: true - '@trpc/react-query@11.0.0-rc.553': - resolution: {integrity: sha512-m88HBhpfIDJccJCXycHtkGS82RG2TVPsZUFIWswR8m6ip5eFNmqWASanu7DiiFbY6sufLT1S8ee28towrPUgoA==} + '@trpc/react-query@11.0.0-rc.569': + resolution: {integrity: sha512-OSIsGeno/g0nMcpi5pQPYCUKPI7rlp7omZyvDvT7mvVQbBexwl7CgFWiWFo0Wv6VlQZYH0klrGsP5J4YRx+C2Q==} peerDependencies: '@tanstack/react-query': ^5.54.1 - '@trpc/client': 11.0.0-rc.553+9b629cc67 - '@trpc/server': 11.0.0-rc.553+9b629cc67 + '@trpc/client': 11.0.0-rc.569+8049266de + '@trpc/server': 11.0.0-rc.569+8049266de react: '>=18.2.0' react-dom: '>=18.2.0' - '@trpc/server@11.0.0-rc.553': - resolution: {integrity: sha512-pYUhxV3QU834jP1ugJk1uhtPwwaY1ymIuupb4DiWOhNCl68syuXPlUdD8IYsYKd3BjLkmnjlsOUMoyd/aoOF/Q==} + '@trpc/server@11.0.0-rc.569': + resolution: {integrity: sha512-ClaeO8fNizYaUllxON7soAoZgX8CUZ9iuiJxajftTlh9W8/r/cyq2v/G9prPl0psdOHvOPJJ31y6aE2Wt+LdMQ==} '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -3370,8 +3470,8 @@ packages: '@types/node@18.19.50': resolution: {integrity: sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==} - '@types/node@20.16.10': - resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} + '@types/node@20.16.11': + resolution: {integrity: sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==} '@types/prismjs@1.26.4': resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==} @@ -3388,8 +3488,8 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react-dom@18.3.0': - resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + '@types/react-dom@18.3.1': + resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} '@types/react@18.3.11': resolution: {integrity: sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==} @@ -3436,8 +3536,8 @@ packages: '@types/ws@8.5.12': resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} - '@typescript-eslint/eslint-plugin@8.8.0': - resolution: {integrity: sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==} + '@typescript-eslint/eslint-plugin@8.8.1': + resolution: {integrity: sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -3447,8 +3547,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.8.0': - resolution: {integrity: sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==} + '@typescript-eslint/parser@8.8.1': + resolution: {integrity: sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3457,12 +3557,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.8.0': - resolution: {integrity: sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==} + '@typescript-eslint/scope-manager@8.8.1': + resolution: {integrity: sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.8.0': - resolution: {integrity: sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==} + '@typescript-eslint/type-utils@8.8.1': + resolution: {integrity: sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -3470,12 +3570,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.8.0': - resolution: {integrity: sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==} + '@typescript-eslint/types@8.8.1': + resolution: {integrity: sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.8.0': - resolution: {integrity: sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==} + '@typescript-eslint/typescript-estree@8.8.1': + resolution: {integrity: sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -3483,29 +3583,25 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.8.0': - resolution: {integrity: sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==} + '@typescript-eslint/utils@8.8.1': + resolution: {integrity: sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.8.0': - resolution: {integrity: sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==} + '@typescript-eslint/visitor-keys@8.8.1': + resolution: {integrity: sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@umami/node@0.4.0': resolution: {integrity: sha512-pyphprbiF7KiDSc+SWZ4/rVM8B5vU27zIiFfEPj2lEqczpI4xAKSp+dM3tlzyRAWJL32fcbCfAaLGhJZQV13Rg==} - '@videojs/http-streaming@3.13.3': - resolution: {integrity: sha512-L7H+iTeqHeZ5PylzOx+pT3CVyzn4TALWYTJKkIc1pDaV/cTVfNGtG+9/vXPAydD+wR/xH1M9/t2JH8tn/DCT4w==} + '@videojs/http-streaming@3.14.2': + resolution: {integrity: sha512-c+sg+rrrSrRekBZxd+sNpzjRteIcOEQRJllqCBcz6MrgSaGJGDzV1xhGSAFnxX8E/xfqQeF060us5474WwYi3Q==} engines: {node: '>=8', npm: '>=5'} peerDependencies: video.js: ^8.14.0 - '@videojs/vhs-utils@3.0.5': - resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==} - engines: {node: '>=8', npm: '>=5'} - '@videojs/vhs-utils@4.0.0': resolution: {integrity: sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==} engines: {node: '>=8', npm: '>=5'} @@ -3668,9 +3764,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - aes-decrypter@4.0.1: - resolution: {integrity: sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==} - aes-decrypter@4.0.2: resolution: {integrity: sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==} @@ -4166,6 +4259,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + cookies@0.9.1: resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} @@ -4347,6 +4444,11 @@ packages: destr@2.0.3: resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -4426,17 +4528,17 @@ packages: resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==} engines: {node: '>=4'} - drizzle-kit@0.24.2: - resolution: {integrity: sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ==} + drizzle-kit@0.25.0: + resolution: {integrity: sha512-Rcf0nYCAKizwjWQCY+d3zytyuTbDb81NcaPor+8NebESlUz1+9W3uGl0+r9FhU4Qal5Zv9j/7neXCSCe7DHzjA==} hasBin: true - drizzle-orm@0.33.0: - resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} + drizzle-orm@0.34.1: + resolution: {integrity: sha512-t+zCwyWWt8xTqtYV4doE/xYmT7hpv1L8pQ66zddEz+3VWyedBBtctjMAp22mAJPfyWurRQXUJ1nrTtqLq+DqNA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' '@cloudflare/workers-types': '>=3' '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' + '@libsql/client': '>=0.10.0' '@neondatabase/serverless': '>=0.1' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 @@ -4711,20 +4813,20 @@ packages: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} - eslint-scope@8.0.2: - resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + eslint-scope@8.1.0: + resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.0.0: - resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + eslint-visitor-keys@4.1.0: + resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.11.1: - resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} + eslint@9.12.0: + resolution: {integrity: sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -4733,8 +4835,8 @@ packages: jiti: optional: true - espree@10.1.0: - resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + espree@10.2.0: + resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: @@ -5463,8 +5565,8 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jose@5.9.2: - resolution: {integrity: sha512-ILI2xx/I57b20sd7rHZvgiiQrmp2mcotwsAH+5ajbpFQbrYVQdNHYlQhoA5cFb78CgtBOxtC05TeA+mcgkuCqQ==} + jose@5.9.3: + resolution: {integrity: sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==} jotai@2.10.0: resolution: {integrity: sha512-8W4u0aRlOIwGlLQ0sqfl/c6+eExl5D8lZgAUolirZLktyaj4WnxO/8a0HEPmtriQAB6X5LMhXzZVmw02X0P0qQ==} @@ -5797,9 +5899,6 @@ packages: engines: {node: '>=10'} hasBin: true - modern-screenshot@4.4.39: - resolution: {integrity: sha512-p+I4yLZUDnoJMa5zoi+71nLQmoLQ6WRU4W8vZu1BZk2PlIYOz5mGnj9/7t2lGWKYeOr4zo6pajhY0/9TS5Zcdw==} - mpd-parser@1.3.0: resolution: {integrity: sha512-WgeIwxAqkmb9uTn4ClicXpEQYCEduDqRKfmUdp4X8vmghKfBNXZLYpREn9eqrDx/Tf5LhzRcJLSpi4ohfV742Q==} hasBin: true @@ -5880,8 +5979,8 @@ packages: next-international@1.2.4: resolution: {integrity: sha512-JQvp+h2iSgA/t8hu5S/Lwow1ZErJutQRdpnplxjv4VTlCiND8T95fYih8BjkHcVhQbtM+Wu9Mb1CM32wD9hlWQ==} - next@14.2.14: - resolution: {integrity: sha512-Q1coZG17MW0Ly5x76shJ4dkC23woLAhhnDnw+DfTc7EpZSGuWrlsZ3bZaO8t6u1Yu8FVfhkqJE+U8GC7E0GLPQ==} + next@14.2.15: + resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -5918,6 +6017,9 @@ packages: node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-cron@3.0.3: resolution: {integrity: sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==} engines: {node: '>=6.0.0'} @@ -5983,8 +6085,11 @@ packages: nwsapi@2.2.12: resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} - oauth4webapi@2.15.0: - resolution: {integrity: sha512-6nZnlxj6M3LeVrxB7bgxwY8hq0B5Quemxhbw85evRlK70qCCUm9O1s47AepSmgYUbkwLTFedP1DZ1eBUIKyRSw==} + oauth4webapi@2.17.0: + resolution: {integrity: sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==} + + oauth4webapi@3.0.0: + resolution: {integrity: sha512-Rw9SxQYuQX9J41VgM4rVNGtm1ng0Qcd8ndv7JmhmwqQ3hHBokX+WjV379IJhKk7bVPHefgvrDgHoO/rB2dY7YA==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -6713,8 +6818,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.79.4: - resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==} + sass@1.79.5: + resolution: {integrity: sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==} engines: {node: '>=14.0.0'} hasBin: true @@ -7344,8 +7449,8 @@ packages: types-ramda@0.30.1: resolution: {integrity: sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==} - typescript-eslint@8.8.0: - resolution: {integrity: sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==} + typescript-eslint@8.8.1: + resolution: {integrity: sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -7353,8 +7458,8 @@ packages: typescript: optional: true - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -7386,8 +7491,8 @@ packages: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} - undici@6.19.8: - resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} + undici@6.20.0: + resolution: {integrity: sha512-AITZfPuxubm31Sx0vr8bteSalEbs9wQb/BOBi9FPlD9Qpd6HxZ4Q0+hI742jBhkPb4RT2v5MQzaW5VhRVyj+9A==} engines: {node: '>=18.17'} unique-string@2.0.0: @@ -7522,8 +7627,8 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - video.js@8.17.4: - resolution: {integrity: sha512-AECieAxKMKB/QgYK36ci50phfpWys6bFT6+pGMpSafeFYSoZaQ2Vpl83T9Qqcesv4TO7oNtiycnVeaBnrva2oA==} + video.js@8.18.1: + resolution: {integrity: sha512-oQ4M/HD2fFgEPHfmVMWxGykRFIpOmVhK0XZ4PSsPTgN2jH6E6+92f/RI2mDXDb0yu+Fxv9fxMUm0M7Z2K3Zo9w==} videojs-contrib-quality-levels@4.1.0: resolution: {integrity: sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==} @@ -7705,8 +7810,8 @@ packages: resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} engines: {node: '>= 12.0.0'} - winston@3.14.2: - resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} + winston@3.15.0: + resolution: {integrity: sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==} engines: {node: '>= 12.0.0'} word-wrap@1.2.5: @@ -7851,14 +7956,24 @@ snapshots: '@panva/hkdf': 1.2.1 '@types/cookie': 0.6.0 cookie: 0.6.0 - jose: 5.9.2 - oauth4webapi: 2.15.0 + jose: 5.9.3 + oauth4webapi: 2.17.0 preact: 10.11.3 preact-render-to-string: 5.2.3(preact@10.11.3) - '@auth/drizzle-adapter@1.5.3': + '@auth/core@0.37.0': dependencies: - '@auth/core': 0.35.3 + '@panva/hkdf': 1.2.1 + '@types/cookie': 0.6.0 + cookie: 0.7.1 + jose: 5.9.3 + oauth4webapi: 3.0.0 + preact: 10.11.3 + preact-render-to-string: 5.2.3(preact@10.11.3) + + '@auth/drizzle-adapter@1.7.0': + dependencies: + '@auth/core': 0.37.0 transitivePeerDependencies: - '@simplewebauthn/browser' - '@simplewebauthn/server' @@ -8362,9 +8477,9 @@ snapshots: '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.11.1)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.12.0)': dependencies: - eslint: 9.11.1 + eslint: 9.12.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.1': {} @@ -8383,7 +8498,7 @@ snapshots: dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 10.1.0 + espree: 10.2.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -8393,7 +8508,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.11.1': {} + '@eslint/js@9.12.0': {} '@eslint/object-schema@2.1.4': {} @@ -8445,9 +8560,16 @@ snapshots: dependencies: hono: 4.6.1 + '@humanfs/core@0.19.0': {} + + '@humanfs/node@0.16.5': + dependencies: + '@humanfs/core': 0.19.0 + '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.0': {} + '@humanwhocodes/retry@0.3.1': {} '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)': dependencies: @@ -8594,7 +8716,7 @@ snapshots: - encoding - supports-color - '@million/install@1.0.8': + '@million/install@1.0.9': dependencies: '@antfu/ni': 0.21.12 '@axiomhq/js': 1.0.0-rc.3 @@ -8608,13 +8730,13 @@ snapshots: recast: 0.23.9 xycolors: 0.1.2 - '@million/lint@1.0.8(rollup@4.21.3)(webpack-sources@3.2.3)': + '@million/lint@1.0.9(rollup@4.21.3)(webpack-sources@3.2.3)': dependencies: '@axiomhq/js': 1.0.0-rc.3 '@babel/core': 7.25.2 '@babel/types': 7.25.2 '@hono/node-server': 1.13.0(hono@4.6.1) - '@million/install': 1.0.8 + '@million/install': 1.0.9 '@rollup/pluginutils': 5.1.0(rollup@4.21.3) '@rrweb/types': 2.0.0-alpha.16 babel-plugin-syntax-hermes-parser: 0.21.1 @@ -8623,7 +8745,6 @@ snapshots: faster-babel-types: 0.1.0(@babel/types@7.25.2) hono: 4.6.1 isomorphic-fetch: 3.0.0 - modern-screenshot: 4.4.39 nanoid: 5.0.7 pako: 2.1.0 pathe: 1.1.2 @@ -8644,37 +8765,37 @@ snapshots: - utf-8-validate - webpack-sources - '@next/env@14.2.14': {} + '@next/env@14.2.15': {} - '@next/eslint-plugin-next@14.2.14': + '@next/eslint-plugin-next@14.2.15': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@14.2.14': + '@next/swc-darwin-arm64@14.2.15': optional: true - '@next/swc-darwin-x64@14.2.14': + '@next/swc-darwin-x64@14.2.15': optional: true - '@next/swc-linux-arm64-gnu@14.2.14': + '@next/swc-linux-arm64-gnu@14.2.15': optional: true - '@next/swc-linux-arm64-musl@14.2.14': + '@next/swc-linux-arm64-musl@14.2.15': optional: true - '@next/swc-linux-x64-gnu@14.2.14': + '@next/swc-linux-x64-gnu@14.2.15': optional: true - '@next/swc-linux-x64-musl@14.2.14': + '@next/swc-linux-x64-musl@14.2.15': optional: true - '@next/swc-win32-arm64-msvc@14.2.14': + '@next/swc-win32-arm64-msvc@14.2.15': optional: true - '@next/swc-win32-ia32-msvc@14.2.14': + '@next/swc-win32-ia32-msvc@14.2.15': optional: true - '@next/swc-win32-x64-msvc@14.2.14': + '@next/swc-win32-x64-msvc@14.2.15': optional: true '@noble/hashes@1.5.0': {} @@ -8697,6 +8818,62 @@ snapshots: dependencies: '@noble/hashes': 1.5.0 + '@parcel/watcher-android-arm64@2.4.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.4.1': + optional: true + + '@parcel/watcher-darwin-x64@2.4.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.4.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.4.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.4.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.4.1': + optional: true + + '@parcel/watcher-win32-arm64@2.4.1': + optional: true + + '@parcel/watcher-win32-ia32@2.4.1': + optional: true + + '@parcel/watcher-win32-x64@2.4.1': + optional: true + + '@parcel/watcher@2.4.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.4.1 + '@parcel/watcher-darwin-arm64': 2.4.1 + '@parcel/watcher-darwin-x64': 2.4.1 + '@parcel/watcher-freebsd-x64': 2.4.1 + '@parcel/watcher-linux-arm-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-glibc': 2.4.1 + '@parcel/watcher-linux-arm64-musl': 2.4.1 + '@parcel/watcher-linux-x64-glibc': 2.4.1 + '@parcel/watcher-linux-x64-musl': 2.4.1 + '@parcel/watcher-win32-arm64': 2.4.1 + '@parcel/watcher-win32-ia32': 2.4.1 + '@parcel/watcher-win32-x64': 2.4.1 + '@pkgjs/parseargs@0.11.0': optional: true @@ -9121,18 +9298,18 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.7.0 - '@t3-oss/env-core@0.11.1(typescript@5.6.2)(zod@3.23.8)': + '@t3-oss/env-core@0.11.1(typescript@5.6.3)(zod@3.23.8)': dependencies: zod: 3.23.8 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 - '@t3-oss/env-nextjs@0.11.1(typescript@5.6.2)(zod@3.23.8)': + '@t3-oss/env-nextjs@0.11.1(typescript@5.6.3)(zod@3.23.8)': dependencies: - '@t3-oss/env-core': 0.11.1(typescript@5.6.2)(zod@3.23.8) + '@t3-oss/env-core': 0.11.1(typescript@5.6.3)(zod@3.23.8) zod: 3.23.8 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 '@tabler/icons-react@3.19.0(react@18.3.1)': dependencies: @@ -9145,25 +9322,25 @@ snapshots: dependencies: remove-accents: 0.5.0 - '@tanstack/query-core@5.59.0': {} + '@tanstack/query-core@5.59.9': {} '@tanstack/query-devtools@5.58.0': {} - '@tanstack/react-query-devtools@5.59.0(@tanstack/react-query@5.59.0(react@18.3.1))(react@18.3.1)': + '@tanstack/react-query-devtools@5.59.9(@tanstack/react-query@5.59.9(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/query-devtools': 5.58.0 - '@tanstack/react-query': 5.59.0(react@18.3.1) + '@tanstack/react-query': 5.59.9(react@18.3.1) react: 18.3.1 - '@tanstack/react-query-next-experimental@5.59.0(@tanstack/react-query@5.59.0(react@18.3.1))(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1)': + '@tanstack/react-query-next-experimental@5.59.9(@tanstack/react-query@5.59.9(react@18.3.1))(next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5))(react@18.3.1)': dependencies: - '@tanstack/react-query': 5.59.0(react@18.3.1) - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + '@tanstack/react-query': 5.59.9(react@18.3.1) + next: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: 18.3.1 - '@tanstack/react-query@5.59.0(react@18.3.1)': + '@tanstack/react-query@5.59.9(react@18.3.1)': dependencies: - '@tanstack/query-core': 5.59.0 + '@tanstack/query-core': 5.59.9 react: 18.3.1 '@tanstack/react-table@8.19.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -9402,30 +9579,30 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553)': + '@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569)': dependencies: - '@trpc/server': 11.0.0-rc.553 + '@trpc/server': 11.0.0-rc.569 - '@trpc/next@11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/react-query@11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@trpc/server@11.0.0-rc.553)(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@trpc/next@11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/react-query@11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@trpc/server@11.0.0-rc.569)(next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@trpc/client': 11.0.0-rc.553(@trpc/server@11.0.0-rc.553) - '@trpc/server': 11.0.0-rc.553 - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + '@trpc/client': 11.0.0-rc.569(@trpc/server@11.0.0-rc.569) + '@trpc/server': 11.0.0-rc.569 + next: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@tanstack/react-query': 5.59.0(react@18.3.1) - '@trpc/react-query': 11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tanstack/react-query': 5.59.9(react@18.3.1) + '@trpc/react-query': 11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@trpc/react-query@11.0.0-rc.553(@tanstack/react-query@5.59.0(react@18.3.1))(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@trpc/react-query@11.0.0-rc.569(@tanstack/react-query@5.59.9(react@18.3.1))(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/react-query': 5.59.0(react@18.3.1) - '@trpc/client': 11.0.0-rc.553(@trpc/server@11.0.0-rc.553) - '@trpc/server': 11.0.0-rc.553 + '@tanstack/react-query': 5.59.9(react@18.3.1) + '@trpc/client': 11.0.0-rc.569(@trpc/server@11.0.0-rc.569) + '@trpc/server': 11.0.0-rc.569 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@trpc/server@11.0.0-rc.553': {} + '@trpc/server@11.0.0-rc.569': {} '@tsconfig/node10@1.0.11': {} @@ -9437,7 +9614,7 @@ snapshots: '@tsconfig/svelte@1.0.13': {} - '@turbo/gen@2.1.3(@types/node@20.16.10)(typescript@5.6.2)': + '@turbo/gen@2.1.3(@types/node@20.16.11)(typescript@5.6.3)': dependencies: '@turbo/workspaces': 2.1.3 commander: 10.0.1 @@ -9447,7 +9624,7 @@ snapshots: node-plop: 0.26.3 picocolors: 1.0.1 proxy-agent: 6.4.0 - ts-node: 10.9.2(@types/node@20.16.10)(typescript@5.6.2) + ts-node: 10.9.2(@types/node@20.16.11)(typescript@5.6.3) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -9474,7 +9651,7 @@ snapshots: '@types/asn1@0.2.4': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/babel__core@7.20.5': dependencies: @@ -9499,22 +9676,22 @@ snapshots: '@types/bcrypt@5.0.2': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/better-sqlite3@7.6.11': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/chroma-js@2.4.4': {} '@types/connect@3.4.38': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/cookie@0.6.0': {} @@ -9523,7 +9700,7 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 4.17.21 '@types/keygrip': 1.0.6 - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/css-font-loading-module@0.0.7': {} @@ -9531,13 +9708,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/ssh2': 1.15.1 '@types/dockerode@3.3.31': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/ssh2': 1.15.1 '@types/estree@1.0.5': {} @@ -9546,7 +9723,7 @@ snapshots: '@types/express-serve-static-core@4.19.5': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/qs': 6.9.16 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -9561,7 +9738,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/hast@2.3.10': dependencies: @@ -9590,7 +9767,7 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.16.10': + '@types/node@20.16.11': dependencies: undici-types: 6.19.8 @@ -9606,7 +9783,7 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react-dom@18.3.0': + '@types/react-dom@18.3.1': dependencies: '@types/react': 18.3.11 @@ -9618,21 +9795,21 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/send': 0.17.4 '@types/ssh2-streams@0.1.12': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/ssh2@0.5.52': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/ssh2-streams': 0.1.12 '@types/ssh2@1.15.1': @@ -9645,7 +9822,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@types/tinycolor2@1.4.6': {} @@ -9661,107 +9838,101 @@ snapshots: '@types/ws@8.5.12': dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 - '@typescript-eslint/eslint-plugin@8.8.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint@9.11.1)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint@9.12.0)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 8.8.0(eslint@9.11.1)(typescript@5.6.2) - '@typescript-eslint/scope-manager': 8.8.0 - '@typescript-eslint/type-utils': 8.8.0(eslint@9.11.1)(typescript@5.6.2) - '@typescript-eslint/utils': 8.8.0(eslint@9.11.1)(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.8.0 - eslint: 9.11.1 + '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.8.1 + '@typescript-eslint/type-utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.8.1 + eslint: 9.12.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2)': + '@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 8.8.0 - '@typescript-eslint/types': 8.8.0 - '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.8.0 + '@typescript-eslint/scope-manager': 8.8.1 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.8.1 debug: 4.3.7 - eslint: 9.11.1 + eslint: 9.12.0 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.8.0': + '@typescript-eslint/scope-manager@8.8.1': dependencies: - '@typescript-eslint/types': 8.8.0 - '@typescript-eslint/visitor-keys': 8.8.0 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/visitor-keys': 8.8.1 - '@typescript-eslint/type-utils@8.8.0(eslint@9.11.1)(typescript@5.6.2)': + '@typescript-eslint/type-utils@8.8.1(eslint@9.12.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.6.2) - '@typescript-eslint/utils': 8.8.0(eslint@9.11.1)(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3) debug: 4.3.7 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - eslint - supports-color - '@typescript-eslint/types@8.8.0': {} + '@typescript-eslint/types@8.8.1': {} - '@typescript-eslint/typescript-estree@8.8.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@8.8.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.8.0 - '@typescript-eslint/visitor-keys': 8.8.0 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/visitor-keys': 8.8.1 debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.8.0(eslint@9.11.1)(typescript@5.6.2)': + '@typescript-eslint/utils@8.8.1(eslint@9.12.0)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1) - '@typescript-eslint/scope-manager': 8.8.0 - '@typescript-eslint/types': 8.8.0 - '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.6.2) - eslint: 9.11.1 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) + '@typescript-eslint/scope-manager': 8.8.1 + '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.3) + eslint: 9.12.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.8.0': + '@typescript-eslint/visitor-keys@8.8.1': dependencies: - '@typescript-eslint/types': 8.8.0 + '@typescript-eslint/types': 8.8.1 eslint-visitor-keys: 3.4.3 '@umami/node@0.4.0': {} - '@videojs/http-streaming@3.13.3(video.js@8.17.4)': + '@videojs/http-streaming@3.14.2(video.js@8.18.1)': dependencies: '@babel/runtime': 7.25.6 - '@videojs/vhs-utils': 4.0.0 - aes-decrypter: 4.0.1 + '@videojs/vhs-utils': 4.1.1 + aes-decrypter: 4.0.2 global: 4.4.0 m3u8-parser: 7.2.0 mpd-parser: 1.3.0 mux.js: 7.0.3 - video.js: 8.17.4 - - '@videojs/vhs-utils@3.0.5': - dependencies: - '@babel/runtime': 7.25.6 - global: 4.4.0 - url-toolkit: 2.2.5 + video.js: 8.18.1 '@videojs/vhs-utils@4.0.0': dependencies: @@ -9780,14 +9951,14 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-react@4.3.2(vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))': + '@vitejs/plugin-react@4.3.2(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))': dependencies: '@babel/core': 7.25.2 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.25.2) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.25.2) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vite: 5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) transitivePeerDependencies: - supports-color @@ -9805,7 +9976,7 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.1.2(@types/node@20.16.10)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vitest: 2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) transitivePeerDependencies: - supports-color @@ -9816,13 +9987,13 @@ snapshots: chai: 5.1.1 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))': + '@vitest/mocker@2.1.2(@vitest/spy@2.1.2)(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0))': dependencies: '@vitest/spy': 2.1.2 estree-walker: 3.0.3 magic-string: 0.30.11 optionalDependencies: - vite: 5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vite: 5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) '@vitest/pretty-format@2.1.2': dependencies: @@ -9852,7 +10023,7 @@ snapshots: sirv: 2.0.4 tinyglobby: 0.2.6 tinyrainbow: 1.2.0 - vitest: 2.1.2(@types/node@20.16.10)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vitest: 2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) '@vitest/utils@2.1.2': dependencies: @@ -9979,13 +10150,6 @@ snapshots: acorn@8.12.1: {} - aes-decrypter@4.0.1: - dependencies: - '@babel/runtime': 7.25.6 - '@videojs/vhs-utils': 3.0.5 - global: 4.4.0 - pkcs7: 1.0.4 - aes-decrypter@4.0.2: dependencies: '@babel/runtime': 7.25.6 @@ -10557,6 +10721,8 @@ snapshots: cookie@0.6.0: {} + cookie@0.7.1: {} + cookies@0.9.1: dependencies: depd: 2.0.0 @@ -10738,6 +10904,8 @@ snapshots: destr@2.0.3: {} + detect-libc@1.0.3: {} + detect-libc@2.0.3: {} detect-node-es@1.1.0: {} @@ -10824,7 +10992,7 @@ snapshots: drange@1.1.1: {} - drizzle-kit@0.24.2: + drizzle-kit@0.25.0: dependencies: '@drizzle-team/brocli': 0.10.1 '@esbuild-kit/esm-loader': 2.6.5 @@ -10833,7 +11001,7 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.33.0(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@11.3.0)(mysql2@3.11.3)(react@18.3.1): + drizzle-orm@0.34.1(@types/better-sqlite3@7.6.11)(@types/react@18.3.11)(better-sqlite3@11.3.0)(mysql2@3.11.3)(react@18.3.1): optionalDependencies: '@types/better-sqlite3': 7.6.11 '@types/react': 18.3.11 @@ -11110,14 +11278,14 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-prettier@9.1.0(eslint@9.11.1): + eslint-config-prettier@9.1.0(eslint@9.12.0): dependencies: - eslint: 9.11.1 + eslint: 9.12.0 - eslint-config-turbo@2.1.3(eslint@9.11.1): + eslint-config-turbo@2.1.3(eslint@9.12.0): dependencies: - eslint: 9.11.1 - eslint-plugin-turbo: 2.1.3(eslint@9.11.1) + eslint: 9.12.0 + eslint-plugin-turbo: 2.1.3(eslint@9.12.0) eslint-import-resolver-node@0.3.9: dependencies: @@ -11127,17 +11295,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.12.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.8.0(eslint@9.11.1)(typescript@5.6.2) - eslint: 9.11.1 + '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.3) + eslint: 9.12.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint@9.11.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint@9.12.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -11146,9 +11314,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.11.1 + eslint: 9.12.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.12.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -11160,13 +11328,13 @@ snapshots: string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.8.0(eslint@9.11.1)(typescript@5.6.2) + '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.0(eslint@9.11.1): + eslint-plugin-jsx-a11y@6.10.0(eslint@9.12.0): dependencies: aria-query: 5.1.3 array-includes: 3.1.8 @@ -11177,7 +11345,7 @@ snapshots: damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 es-iterator-helpers: 1.0.19 - eslint: 9.11.1 + eslint: 9.12.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -11186,11 +11354,11 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 - eslint-plugin-react-hooks@4.6.2(eslint@9.11.1): + eslint-plugin-react-hooks@4.6.2(eslint@9.12.0): dependencies: - eslint: 9.11.1 + eslint: 9.12.0 - eslint-plugin-react@7.37.1(eslint@9.11.1): + eslint-plugin-react@7.37.1(eslint@9.12.0): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 @@ -11198,7 +11366,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.0.19 - eslint: 9.11.1 + eslint: 9.12.0 estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -11212,37 +11380,37 @@ snapshots: string.prototype.matchall: 4.0.11 string.prototype.repeat: 1.0.0 - eslint-plugin-turbo@2.1.3(eslint@9.11.1): + eslint-plugin-turbo@2.1.3(eslint@9.12.0): dependencies: dotenv: 16.0.3 - eslint: 9.11.1 + eslint: 9.12.0 eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 - eslint-scope@8.0.2: + eslint-scope@8.1.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.0.0: {} + eslint-visitor-keys@4.1.0: {} - eslint@9.11.1: + eslint@9.12.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) '@eslint-community/regexpp': 4.11.1 '@eslint/config-array': 0.18.0 '@eslint/core': 0.6.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.11.1 + '@eslint/js': 9.12.0 '@eslint/plugin-kit': 0.2.0 + '@humanfs/node': 0.16.5 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.0 - '@nodelib/fs.walk': 1.2.8 + '@humanwhocodes/retry': 0.3.1 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 ajv: 6.12.6 @@ -11250,9 +11418,9 @@ snapshots: cross-spawn: 7.0.3 debug: 4.3.7 escape-string-regexp: 4.0.0 - eslint-scope: 8.0.2 - eslint-visitor-keys: 4.0.0 - espree: 10.1.0 + eslint-scope: 8.1.0 + eslint-visitor-keys: 4.1.0 + espree: 10.2.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -11262,22 +11430,20 @@ snapshots: ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color - espree@10.1.0: + espree@10.2.0: dependencies: acorn: 8.12.1 acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.0.0 + eslint-visitor-keys: 4.1.0 esprima@4.0.1: {} @@ -12025,11 +12191,11 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 merge-stream: 2.0.0 supports-color: 8.1.1 - jose@5.9.2: {} + jose@5.9.3: {} jotai@2.10.0(@types/react@18.3.11)(react@18.3.1): optionalDependencies: @@ -12355,8 +12521,6 @@ snapshots: mkdirp@1.0.4: {} - modern-screenshot@4.4.39: {} - mpd-parser@1.3.0: dependencies: '@babel/runtime': 7.25.6 @@ -12410,10 +12574,10 @@ snapshots: netmask@2.0.2: {} - next-auth@5.0.0-beta.22(next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4))(react@18.3.1): + next-auth@5.0.0-beta.22(next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5))(react@18.3.1): dependencies: '@auth/core': 0.35.3 - next: 14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4) + next: 14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5) react: 18.3.1 next-international@1.2.4: @@ -12422,9 +12586,9 @@ snapshots: international-types: 0.8.1 server-only: 0.0.1 - next@14.2.14(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.4): + next@14.2.15(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.79.5): dependencies: - '@next/env': 14.2.14 + '@next/env': 14.2.15 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001660 @@ -12434,16 +12598,16 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.14 - '@next/swc-darwin-x64': 14.2.14 - '@next/swc-linux-arm64-gnu': 14.2.14 - '@next/swc-linux-arm64-musl': 14.2.14 - '@next/swc-linux-x64-gnu': 14.2.14 - '@next/swc-linux-x64-musl': 14.2.14 - '@next/swc-win32-arm64-msvc': 14.2.14 - '@next/swc-win32-ia32-msvc': 14.2.14 - '@next/swc-win32-x64-msvc': 14.2.14 - sass: 1.79.4 + '@next/swc-darwin-arm64': 14.2.15 + '@next/swc-darwin-x64': 14.2.15 + '@next/swc-linux-arm64-gnu': 14.2.15 + '@next/swc-linux-arm64-musl': 14.2.15 + '@next/swc-linux-x64-gnu': 14.2.15 + '@next/swc-linux-x64-musl': 14.2.15 + '@next/swc-win32-arm64-msvc': 14.2.15 + '@next/swc-win32-ia32-msvc': 14.2.15 + '@next/swc-win32-x64-msvc': 14.2.15 + sass: 1.79.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -12469,6 +12633,8 @@ snapshots: node-addon-api@5.1.0: {} + node-addon-api@7.1.1: {} + node-cron@3.0.3: dependencies: uuid: 8.3.2 @@ -12497,7 +12663,7 @@ snapshots: node-mocks-http@1.16.0: dependencies: '@types/express': 4.17.21 - '@types/node': 20.16.10 + '@types/node': 20.16.11 accepts: 1.3.8 content-disposition: 0.5.4 depd: 1.1.2 @@ -12544,7 +12710,9 @@ snapshots: nwsapi@2.2.12: {} - oauth4webapi@2.15.0: {} + oauth4webapi@2.17.0: {} + + oauth4webapi@3.0.0: {} object-assign@4.1.1: {} @@ -13392,8 +13560,9 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.79.4: + sass@1.79.5: dependencies: + '@parcel/watcher': 2.4.1 chokidar: 4.0.0 immutable: 4.3.7 source-map-js: 1.2.1 @@ -13980,10 +14149,10 @@ snapshots: triple-beam@1.4.1: {} - trpc-swagger@1.2.6(patch_hash=6s72z7zx33c52iesv5sewipn6i)(@trpc/client@11.0.0-rc.553(@trpc/server@11.0.0-rc.553))(@trpc/server@11.0.0-rc.553)(zod@3.23.8): + trpc-swagger@1.2.6(patch_hash=6s72z7zx33c52iesv5sewipn6i)(@trpc/client@11.0.0-rc.569(@trpc/server@11.0.0-rc.569))(@trpc/server@11.0.0-rc.569)(zod@3.23.8): dependencies: - '@trpc/client': 11.0.0-rc.553(@trpc/server@11.0.0-rc.553) - '@trpc/server': 11.0.0-rc.553 + '@trpc/client': 11.0.0-rc.569(@trpc/server@11.0.0-rc.569) + '@trpc/server': 11.0.0-rc.569 chalk-scripts: 1.2.8 co-body: 6.2.0 lodash.clonedeep: 4.5.0 @@ -13992,35 +14161,35 @@ snapshots: zod: 3.23.8 zod-to-json-schema: 3.23.3(zod@3.23.8) - ts-api-utils@1.3.0(typescript@5.6.2): + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: - typescript: 5.6.2 + typescript: 5.6.3 ts-mixer@6.0.4: {} - ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.2): + ts-node@10.9.2(@types/node@20.16.11)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.16.10 + '@types/node': 20.16.11 acorn: 8.12.1 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.6.2 + typescript: 5.6.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 ts-toolbelt@9.6.0: {} - tsconfck@3.1.3(typescript@5.6.2): + tsconfck@3.1.3(typescript@5.6.3): optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 tsconfig-paths@3.15.0: dependencies: @@ -14130,18 +14299,18 @@ snapshots: dependencies: ts-toolbelt: 9.6.0 - typescript-eslint@8.8.0(eslint@9.11.1)(typescript@5.6.2): + typescript-eslint@8.8.1(eslint@9.12.0)(typescript@5.6.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.8.0(@typescript-eslint/parser@8.8.0(eslint@9.11.1)(typescript@5.6.2))(eslint@9.11.1)(typescript@5.6.2) - '@typescript-eslint/parser': 8.8.0(eslint@9.11.1)(typescript@5.6.2) - '@typescript-eslint/utils': 8.8.0(eslint@9.11.1)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.3))(eslint@9.12.0)(typescript@5.6.3) + '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - eslint - supports-color - typescript@5.6.2: {} + typescript@5.6.3: {} uc.micro@2.1.0: {} @@ -14167,7 +14336,7 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 - undici@6.19.8: {} + undici@6.20.0: {} unique-string@2.0.0: dependencies: @@ -14291,25 +14460,25 @@ snapshots: validate-npm-package-name@5.0.1: {} - video.js@8.17.4: + video.js@8.18.1: dependencies: '@babel/runtime': 7.25.6 - '@videojs/http-streaming': 3.13.3(video.js@8.17.4) - '@videojs/vhs-utils': 4.0.0 + '@videojs/http-streaming': 3.14.2(video.js@8.18.1) + '@videojs/vhs-utils': 4.1.1 '@videojs/xhr': 2.7.0 aes-decrypter: 4.0.2 global: 4.4.0 m3u8-parser: 7.2.0 mpd-parser: 1.3.0 mux.js: 7.0.3 - videojs-contrib-quality-levels: 4.1.0(video.js@8.17.4) + videojs-contrib-quality-levels: 4.1.0(video.js@8.18.1) videojs-font: 4.2.0 videojs-vtt.js: 0.15.5 - videojs-contrib-quality-levels@4.1.0(video.js@8.17.4): + videojs-contrib-quality-levels@4.1.0(video.js@8.18.1): dependencies: global: 4.4.0 - video.js: 8.17.4 + video.js: 8.18.1 videojs-font@4.2.0: {} @@ -14317,12 +14486,12 @@ snapshots: dependencies: global: 4.4.0 - vite-node@2.1.2(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0): + vite-node@2.1.2(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 - vite: 5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vite: 5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) transitivePeerDependencies: - '@types/node' - less @@ -14334,33 +14503,33 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.0.1(typescript@5.6.2)(vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)): + vite-tsconfig-paths@5.0.1(typescript@5.6.3)(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)): dependencies: debug: 4.3.7 globrex: 0.1.2 - tsconfck: 3.1.3(typescript@5.6.2) + tsconfck: 3.1.3(typescript@5.6.3) optionalDependencies: - vite: 5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vite: 5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) transitivePeerDependencies: - supports-color - typescript - vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0): + vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.21.3 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 fsevents: 2.3.3 - sass: 1.79.4 + sass: 1.79.5 sugarss: 4.0.1(postcss@8.4.47) terser: 5.32.0 - vitest@2.1.2(@types/node@20.16.10)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0): + vitest@2.1.2(@types/node@20.16.11)(@vitest/ui@2.1.2)(jsdom@25.0.1)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0): dependencies: '@vitest/expect': 2.1.2 - '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) + '@vitest/mocker': 2.1.2(@vitest/spy@2.1.2)(vite@5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0)) '@vitest/pretty-format': 2.1.2 '@vitest/runner': 2.1.2 '@vitest/snapshot': 2.1.2 @@ -14375,11 +14544,11 @@ snapshots: tinyexec: 0.3.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.5(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) - vite-node: 2.1.2(@types/node@20.16.10)(sass@1.79.4)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vite: 5.4.5(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) + vite-node: 2.1.2(@types/node@20.16.11)(sass@1.79.5)(sugarss@4.0.1(postcss@8.4.47))(terser@5.32.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.16.10 + '@types/node': 20.16.11 '@vitest/ui': 2.1.2(vitest@2.1.2) jsdom: 25.0.1 transitivePeerDependencies: @@ -14530,7 +14699,7 @@ snapshots: readable-stream: 3.6.2 triple-beam: 1.4.1 - winston@3.14.2: + winston@3.15.0: dependencies: '@colors/colors': 1.6.0 '@dabh/diagnostics': 2.0.3 diff --git a/tooling/eslint/package.json b/tooling/eslint/package.json index 89480d714..921d75abd 100644 --- a/tooling/eslint/package.json +++ b/tooling/eslint/package.json @@ -16,20 +16,20 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@next/eslint-plugin-next": "^14.2.14", + "@next/eslint-plugin-next": "^14.2.15", "eslint-config-prettier": "^9.1.0", "eslint-config-turbo": "^2.1.3", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.1", "eslint-plugin-react-hooks": "^4.6.2", - "typescript-eslint": "^8.8.0" + "typescript-eslint": "^8.8.1" }, "devDependencies": { "@homarr/prettier-config": "workspace:^0.1.0", "@homarr/tsconfig": "workspace:^0.1.0", - "eslint": "^9.11.1", - "typescript": "^5.6.2" + "eslint": "^9.12.0", + "typescript": "^5.6.3" }, "prettier": "@homarr/prettier-config" } diff --git a/tooling/prettier/package.json b/tooling/prettier/package.json index 55461d536..619a59f82 100644 --- a/tooling/prettier/package.json +++ b/tooling/prettier/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@homarr/tsconfig": "workspace:^0.1.0", - "typescript": "^5.6.2" + "typescript": "^5.6.3" }, "prettier": "@homarr/prettier-config" } diff --git a/turbo.json b/turbo.json index 67e69d087..d4eea04f3 100644 --- a/turbo.json +++ b/turbo.json @@ -18,6 +18,7 @@ "AUTH_OIDC_CLIENT_SECRET", "AUTH_OIDC_ISSUER", "AUTH_OIDC_SCOPE_OVERWRITE", + "AUTH_OIDC_GROUPS_ATTRIBUTE", "AUTH_LDAP_USERNAME_ATTRIBUTE", "AUTH_LDAP_USER_MAIL_ATTRIBUTE", "AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG", diff --git a/vitest.config.mts b/vitest.config.mts index 5a9efb391..5c07aef12 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -8,6 +8,7 @@ export default defineConfig({ setupFiles: ["./vitest.setup.ts"], environment: "jsdom", include: ["**/*.spec.ts"], + clearMocks: true, poolOptions: { threads: { singleThread: false, @@ -18,7 +19,7 @@ export default defineConfig({ reporter: ["html", "json-summary", "json"], all: true, exclude: ["apps/nextjs/.next/"], - reportOnFailure: true + reportOnFailure: true, }, exclude: [...configDefaults.exclude, "apps/nextjs/.next"],