refactor: remove central validation export to improve typescript performance (#2810)
* refactor: remove central validation export to improve typescript performance * fix: missing package exports change in validation package * chore: address pull request feedback
This commit is contained in:
@@ -9,7 +9,7 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput } from "@homarr/ui";
|
import { CustomPasswordInput } from "@homarr/ui";
|
||||||
import { validation } from "@homarr/validation";
|
import { userRegistrationSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
interface RegistrationFormProps {
|
interface RegistrationFormProps {
|
||||||
invite: {
|
invite: {
|
||||||
@@ -22,7 +22,7 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => {
|
|||||||
const t = useScopedI18n("user");
|
const t = useScopedI18n("user");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutate, isPending } = clientApi.user.register.useMutation();
|
const { mutate, isPending } = clientApi.user.register.useMutation();
|
||||||
const form = useZodForm(validation.user.registration, {
|
const form = useZodForm(userRegistrationSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
@@ -30,7 +30,7 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (values: z.infer<typeof validation.user.registration>) => {
|
const handleSubmit = (values: z.infer<typeof userRegistrationSchema>) => {
|
||||||
mutate(
|
mutate(
|
||||||
{
|
{
|
||||||
...values,
|
...values,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { useForm } from "@homarr/form";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { userSignInSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
interface LoginFormProps {
|
interface LoginFormProps {
|
||||||
providers: string[];
|
providers: string[];
|
||||||
@@ -22,7 +22,7 @@ interface LoginFormProps {
|
|||||||
callbackUrl: string;
|
callbackUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extendedValidation = validation.user.signIn.extend({ provider: z.enum(["credentials", "ldap"]) });
|
const extendedValidation = userSignInSchema.extend({ provider: z.enum(["credentials", "ldap"]) });
|
||||||
|
|
||||||
export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => {
|
export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => {
|
||||||
const t = useScopedI18n("user");
|
const t = useScopedI18n("user");
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useDisclosure } from "@mantine/hooks";
|
|||||||
|
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
import type { Board } from "../../_types";
|
import type { Board } from "../../_types";
|
||||||
import { generateColors } from "../../(content)/_theme";
|
import { generateColors } from "../../(content)/_theme";
|
||||||
@@ -35,7 +35,7 @@ const hexRegex = /^#[0-9a-fA-F]{6}$/;
|
|||||||
const progressPercentageLabel = (value: number) => `${value}%`;
|
const progressPercentageLabel = (value: number) => `${value}%`;
|
||||||
|
|
||||||
export const ColorSettingsContent = ({ board }: Props) => {
|
export const ColorSettingsContent = ({ board }: Props) => {
|
||||||
const form = useZodForm(validation.board.savePartialSettings, {
|
const form = useZodForm(boardSavePartialSettingsSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
primaryColor: board.primaryColor,
|
primaryColor: board.primaryColor,
|
||||||
secondaryColor: board.secondaryColor,
|
secondaryColor: board.secondaryColor,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { TranslationObject } from "@homarr/translation";
|
|||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import type { SelectItemWithDescriptionBadge } from "@homarr/ui";
|
import type { SelectItemWithDescriptionBadge } from "@homarr/ui";
|
||||||
import { SelectWithDescriptionBadge } from "@homarr/ui";
|
import { SelectWithDescriptionBadge } from "@homarr/ui";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
import type { Board } from "../../_types";
|
import type { Board } from "../../_types";
|
||||||
import { useSavePartialSettingsMutation } from "./_shared";
|
import { useSavePartialSettingsMutation } from "./_shared";
|
||||||
@@ -24,7 +24,7 @@ export const BackgroundSettingsContent = ({ board }: Props) => {
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||||
const form = useZodForm(validation.board.savePartialSettings, {
|
const form = useZodForm(boardSavePartialSettingsSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
backgroundImageUrl: board.backgroundImageUrl ?? "",
|
backgroundImageUrl: board.backgroundImageUrl ?? "",
|
||||||
backgroundImageAttachment: board.backgroundImageAttachment,
|
backgroundImageAttachment: board.backgroundImageAttachment,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useUpdateBoard } from "@homarr/boards/updater";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { IconPicker } from "@homarr/forms-collection";
|
import { IconPicker } from "@homarr/forms-collection";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardSavePartialSettingsSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
import { createMetaTitle } from "~/metadata";
|
import { createMetaTitle } from "~/metadata";
|
||||||
import type { Board } from "../../_types";
|
import type { Board } from "../../_types";
|
||||||
@@ -28,7 +28,7 @@ export const GeneralSettingsContent = ({ board }: Props) => {
|
|||||||
|
|
||||||
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
const { mutate: savePartialSettings, isPending } = useSavePartialSettingsMutation(board);
|
||||||
const form = useZodForm(
|
const form = useZodForm(
|
||||||
validation.board.savePartialSettings
|
boardSavePartialSettingsSchema
|
||||||
.pick({
|
.pick({
|
||||||
pageTitle: true,
|
pageTitle: true,
|
||||||
logoImageUrl: true,
|
logoImageUrl: true,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { clientApi } from "@homarr/api/client";
|
|||||||
import { createId } from "@homarr/db/client";
|
import { createId } from "@homarr/db/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardSaveLayoutsSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
import type { Board } from "../../_types";
|
import type { Board } from "../../_types";
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ export const LayoutSettingsContent = ({ board }: Props) => {
|
|||||||
void utils.board.getHomeBoard.invalidate();
|
void utils.board.getHomeBoard.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.board.saveLayouts.omit({ id: true }).required(), {
|
const form = useZodForm(boardSaveLayoutsSchema.omit({ id: true }).required(), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
layouts: board.layouts,
|
layouts: board.layouts,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,18 +8,18 @@ import { clientApi } from "@homarr/api/client";
|
|||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { groupCreateSchema } from "@homarr/validation/group";
|
||||||
|
|
||||||
export const InitGroup = () => {
|
export const InitGroup = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { mutateAsync } = clientApi.group.createInitialExternalGroup.useMutation();
|
const { mutateAsync } = clientApi.group.createInitialExternalGroup.useMutation();
|
||||||
const form = useZodForm(validation.group.create, {
|
const form = useZodForm(groupCreateSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: "",
|
name: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmitAsync = async (values: z.infer<typeof validation.group.create>) => {
|
const handleSubmitAsync = async (values: z.infer<typeof groupCreateSchema>) => {
|
||||||
await mutateAsync(values, {
|
await mutateAsync(values, {
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
await revalidatePathActionAsync("/init");
|
await revalidatePathActionAsync("/init");
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import type { CheckboxProps } from "@homarr/form/types";
|
|||||||
import { defaultServerSettings } from "@homarr/server-settings";
|
import { defaultServerSettings } from "@homarr/server-settings";
|
||||||
import type { TranslationObject } from "@homarr/translation";
|
import type { TranslationObject } from "@homarr/translation";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { settingsInitSchema } from "@homarr/validation/settings";
|
||||||
|
|
||||||
export const InitSettings = () => {
|
export const InitSettings = () => {
|
||||||
const tSection = useScopedI18n("management.page.settings.section");
|
const tSection = useScopedI18n("management.page.settings.section");
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { mutateAsync } = clientApi.serverSettings.initSettings.useMutation();
|
const { mutateAsync } = clientApi.serverSettings.initSettings.useMutation();
|
||||||
const form = useZodForm(validation.settings.init, { initialValues: defaultServerSettings });
|
const form = useZodForm(settingsInitSchema, { initialValues: defaultServerSettings });
|
||||||
|
|
||||||
form.watch("analytics.enableGeneral", ({ value }) => {
|
form.watch("analytics.enableGeneral", ({ value }) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -30,7 +30,7 @@ export const InitSettings = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmitAsync = async (values: z.infer<typeof validation.settings.init>) => {
|
const handleSubmitAsync = async (values: z.infer<typeof settingsInitSchema>) => {
|
||||||
await mutateAsync(values, {
|
await mutateAsync(values, {
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
await revalidatePathActionAsync("/init");
|
await revalidatePathActionAsync("/init");
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput } from "@homarr/ui";
|
import { CustomPasswordInput } from "@homarr/ui";
|
||||||
import { validation } from "@homarr/validation";
|
import { userInitSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
export const InitUserForm = () => {
|
export const InitUserForm = () => {
|
||||||
const t = useScopedI18n("user");
|
const t = useScopedI18n("user");
|
||||||
const tUser = useScopedI18n("init.step.user");
|
const tUser = useScopedI18n("init.step.user");
|
||||||
const { mutateAsync, isPending } = clientApi.user.initUser.useMutation();
|
const { mutateAsync, isPending } = clientApi.user.initUser.useMutation();
|
||||||
const form = useZodForm(validation.user.init, {
|
const form = useZodForm(userInitSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
@@ -74,4 +74,4 @@ export const InitUserForm = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.user.init>;
|
type FormType = z.infer<typeof userInitSchema>;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { AppForm } from "@homarr/forms-collection";
|
import { AppForm } from "@homarr/forms-collection";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { appManageSchema } from "@homarr/validation/app";
|
||||||
|
|
||||||
interface AppEditFormProps {
|
interface AppEditFormProps {
|
||||||
app: RouterOutputs["app"]["byId"];
|
app: RouterOutputs["app"]["byId"];
|
||||||
@@ -40,7 +40,7 @@ export const AppEditForm = ({ app }: AppEditFormProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(values: z.infer<typeof validation.app.manage>) => {
|
(values: z.infer<typeof appManageSchema>) => {
|
||||||
mutate({
|
mutate({
|
||||||
id: app.id,
|
id: app.id,
|
||||||
...values,
|
...values,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { convertIntegrationTestConnectionError } from "@homarr/integrations/clie
|
|||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { integrationUpdateSchema } from "@homarr/validation/integration";
|
||||||
|
|
||||||
import { SecretCard } from "../../_components/secrets/integration-secret-card";
|
import { SecretCard } from "../../_components/secrets/integration-secret-card";
|
||||||
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
|
import { IntegrationSecretInput } from "../../_components/secrets/integration-secret-inputs";
|
||||||
@@ -32,7 +32,7 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
|||||||
) ?? getDefaultSecretKinds(integration.kind);
|
) ?? getDefaultSecretKinds(integration.kind);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const form = useZodForm(validation.integration.update.omit({ id: true }), {
|
const form = useZodForm(integrationUpdateSchema.omit({ id: true }), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: integration.name,
|
name: integration.name,
|
||||||
url: integration.url,
|
url: integration.url,
|
||||||
@@ -141,4 +141,4 @@ export const EditIntegrationForm = ({ integration }: EditIntegrationForm) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormType = Omit<z.infer<typeof validation.integration.update>, "id">;
|
type FormType = Omit<z.infer<typeof integrationUpdateSchema>, "id">;
|
||||||
|
|||||||
@@ -27,20 +27,21 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
import { convertIntegrationTestConnectionError } from "@homarr/integrations/client";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { appHrefSchema } from "@homarr/validation/app";
|
||||||
|
import { integrationCreateSchema } from "@homarr/validation/integration";
|
||||||
|
|
||||||
import { IntegrationSecretInput } from "../_components/secrets/integration-secret-inputs";
|
import { IntegrationSecretInput } from "../_components/secrets/integration-secret-inputs";
|
||||||
|
|
||||||
interface NewIntegrationFormProps {
|
interface NewIntegrationFormProps {
|
||||||
searchParams: Partial<z.infer<typeof validation.integration.create>> & {
|
searchParams: Partial<z.infer<typeof integrationCreateSchema>> & {
|
||||||
kind: IntegrationKind;
|
kind: IntegrationKind;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const formSchema = validation.integration.create.omit({ kind: true }).and(
|
const formSchema = integrationCreateSchema.omit({ kind: true }).and(
|
||||||
z.object({
|
z.object({
|
||||||
createApp: z.boolean(),
|
createApp: z.boolean(),
|
||||||
appHref: validation.app.manage.shape.href,
|
appHref: appHrefSchema,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import type { IntegrationKind } from "@homarr/definitions";
|
|||||||
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
import { getIntegrationName, integrationKinds } from "@homarr/definitions";
|
||||||
import { getScopedI18n } from "@homarr/translation/server";
|
import { getScopedI18n } from "@homarr/translation/server";
|
||||||
import { IntegrationAvatar } from "@homarr/ui";
|
import { IntegrationAvatar } from "@homarr/ui";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { integrationCreateSchema } from "@homarr/validation/integration";
|
||||||
|
|
||||||
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
|
||||||
import { NewIntegrationForm } from "./_integration-new-form";
|
import { NewIntegrationForm } from "./_integration-new-form";
|
||||||
|
|
||||||
interface NewIntegrationPageProps {
|
interface NewIntegrationPageProps {
|
||||||
searchParams: Promise<
|
searchParams: Promise<
|
||||||
Partial<z.infer<typeof validation.integration.create>> & {
|
Partial<z.infer<typeof integrationCreateSchema>> & {
|
||||||
kind: IntegrationKind;
|
kind: IntegrationKind;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { IconPicker } from "@homarr/forms-collection";
|
import { IconPicker } from "@homarr/forms-collection";
|
||||||
import type { TranslationFunction } from "@homarr/translation";
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.searchEngine.manage>;
|
type FormType = z.infer<typeof searchEngineManageSchema>;
|
||||||
|
|
||||||
interface SearchEngineFormProps {
|
interface SearchEngineFormProps {
|
||||||
submitButtonTranslation: (t: TranslationFunction) => string;
|
submitButtonTranslation: (t: TranslationFunction) => string;
|
||||||
@@ -30,7 +30,7 @@ export const SearchEngineForm = (props: SearchEngineFormProps) => {
|
|||||||
|
|
||||||
const [integrationData] = clientApi.integration.allThatSupportSearch.useSuspenseQuery();
|
const [integrationData] = clientApi.integration.allThatSupportSearch.useSuspenseQuery();
|
||||||
|
|
||||||
const form = useZodForm(validation.searchEngine.manage, {
|
const form = useZodForm(searchEngineManageSchema, {
|
||||||
initialValues: initialValues ?? {
|
initialValues: initialValues ?? {
|
||||||
name: "",
|
name: "",
|
||||||
short: "",
|
short: "",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import type { TranslationFunction } from "@homarr/translation";
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||||
|
|
||||||
import { SearchEngineForm } from "../../_form";
|
import { SearchEngineForm } from "../../_form";
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export const SearchEngineEditForm = ({ searchEngine }: SearchEngineEditFormProps
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(values: z.infer<typeof validation.searchEngine.manage>) => {
|
(values: z.infer<typeof searchEngineManageSchema>) => {
|
||||||
mutate({
|
mutate({
|
||||||
id: searchEngine.id,
|
id: searchEngine.id,
|
||||||
...values,
|
...values,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import type { TranslationFunction } from "@homarr/translation";
|
import type { TranslationFunction } from "@homarr/translation";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||||
|
|
||||||
import { SearchEngineForm } from "../_form";
|
import { SearchEngineForm } from "../_form";
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export const SearchEngineNewForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(values: z.infer<typeof validation.searchEngine.manage>) => {
|
(values: z.infer<typeof searchEngineManageSchema>) => {
|
||||||
mutate(values);
|
mutate(values);
|
||||||
},
|
},
|
||||||
[mutate],
|
[mutate],
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { createModal, useModalAction } from "@homarr/modals";
|
import { createModal, useModalAction } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { superRefineCertificateFile } from "@homarr/validation";
|
import { superRefineCertificateFile } from "@homarr/validation/certificates";
|
||||||
|
|
||||||
export const AddCertificateButton = () => {
|
export const AddCertificateButton = () => {
|
||||||
const { openModal } = useModalAction(AddCertificateModal);
|
const { openModal } = useModalAction(AddCertificateModal);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { userChangeHomeBoardsSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
import type { Board } from "~/app/[locale]/boards/_types";
|
import type { Board } from "~/app/[locale]/boards/_types";
|
||||||
import { BoardSelect } from "~/components/board/board-select";
|
import { BoardSelect } from "~/components/board/board-select";
|
||||||
@@ -40,7 +40,7 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.user.changeHomeBoards, {
|
const form = useZodForm(userChangeHomeBoardsSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
homeBoardId: user.homeBoardId,
|
homeBoardId: user.homeBoardId,
|
||||||
mobileHomeBoardId: user.mobileHomeBoardId,
|
mobileHomeBoardId: user.mobileHomeBoardId,
|
||||||
@@ -82,4 +82,4 @@ export const ChangeHomeBoardForm = ({ user, boardsData }: ChangeHomeBoardFormPro
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.user.changeHomeBoards>;
|
type FormType = z.infer<typeof userChangeHomeBoardsSchema>;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { userChangeSearchPreferencesSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
interface ChangeSearchPreferencesFormProps {
|
interface ChangeSearchPreferencesFormProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
@@ -37,7 +37,7 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.user.changeSearchPreferences, {
|
const form = useZodForm(userChangeSearchPreferencesSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
defaultSearchEngineId: user.defaultSearchEngineId,
|
defaultSearchEngineId: user.defaultSearchEngineId,
|
||||||
openInNewTab: user.openSearchInNewTab,
|
openInNewTab: user.openSearchInNewTab,
|
||||||
@@ -75,4 +75,4 @@ export const ChangeSearchPreferencesForm = ({ user, searchEnginesData }: ChangeS
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.user.changeSearchPreferences>;
|
type FormType = z.infer<typeof userChangeSearchPreferencesSchema>;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { userFirstDayOfWeekSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
dayjs.extend(localeData);
|
dayjs.extend(localeData);
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.user.firstDayOfWeek, {
|
const form = useZodForm(userFirstDayOfWeekSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
|
firstDayOfWeek: user.firstDayOfWeek as DayOfWeek,
|
||||||
},
|
},
|
||||||
@@ -80,4 +80,4 @@ export const FirstDayOfWeek = ({ user }: FirstDayOfWeekProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.user.firstDayOfWeek>;
|
type FormType = z.infer<typeof userFirstDayOfWeekSchema>;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { userPingIconsEnabledSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
interface PingIconsEnabledProps {
|
interface PingIconsEnabledProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
@@ -35,7 +35,7 @@ export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.user.pingIconsEnabled, {
|
const form = useZodForm(userPingIconsEnabledSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
pingIconsEnabled: user.pingIconsEnabled,
|
pingIconsEnabled: user.pingIconsEnabled,
|
||||||
},
|
},
|
||||||
@@ -66,4 +66,4 @@ export const PingIconsEnabled = ({ user }: PingIconsEnabledProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.user.pingIconsEnabled>;
|
type FormType = z.infer<typeof userPingIconsEnabledSchema>;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { userEditProfileSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
interface UserProfileFormProps {
|
interface UserProfileFormProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
@@ -43,7 +43,7 @@ export const UserProfileForm = ({ user }: UserProfileFormProps) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.user.editProfile.omit({ id: true }), {
|
const form = useZodForm(userEditProfileSchema.omit({ id: true }), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: user.name ?? "",
|
name: user.name ?? "",
|
||||||
email: user.email ?? "",
|
email: user.email ?? "",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput } from "@homarr/ui";
|
import { CustomPasswordInput } from "@homarr/ui";
|
||||||
import { validation } from "@homarr/validation";
|
import { userChangePasswordSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
interface ChangePasswordFormProps {
|
interface ChangePasswordFormProps {
|
||||||
user: RouterOutputs["user"]["getById"];
|
user: RouterOutputs["user"]["getById"];
|
||||||
@@ -34,7 +34,7 @@ export const ChangePasswordForm = ({ user }: ChangePasswordFormProps) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.user.changePassword, {
|
const form = useZodForm(userChangePasswordSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
/* Require previous password if the current user want's to change his password */
|
/* Require previous password if the current user want's to change his password */
|
||||||
previousPassword: session?.user.id === user.id ? "" : "_",
|
previousPassword: session?.user.id === user.id ? "" : "_",
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ import { useModalAction } from "@homarr/modals";
|
|||||||
import { showErrorNotification } from "@homarr/notifications";
|
import { showErrorNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput, UserAvatar } from "@homarr/ui";
|
import { CustomPasswordInput, UserAvatar } from "@homarr/ui";
|
||||||
import { validation } from "@homarr/validation";
|
import { createCustomErrorParams } from "@homarr/validation/form/i18n";
|
||||||
import { createCustomErrorParams } from "@homarr/validation/form";
|
import { userPasswordSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
import { GroupSelectModal } from "~/components/access/group-select-modal";
|
import { GroupSelectModal } from "~/components/access/group-select-modal";
|
||||||
import { StepperNavigationComponent } from "./stepper-navigation";
|
import { StepperNavigationComponent } from "./stepper-navigation";
|
||||||
@@ -84,7 +84,7 @@ export const UserCreateStepperComponent = ({ initialGroups }: UserCreateStepperC
|
|||||||
const securityForm = useZodForm(
|
const securityForm = useZodForm(
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
password: validation.user.password,
|
password: userPasswordSchema,
|
||||||
confirmPassword: z.string(),
|
confirmPassword: z.string(),
|
||||||
})
|
})
|
||||||
.refine((data) => data.password === data.confirmPassword, {
|
.refine((data) => data.password === data.confirmPassword, {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { groupUpdateSchema } from "@homarr/validation/group";
|
||||||
|
|
||||||
interface RenameGroupFormProps {
|
interface RenameGroupFormProps {
|
||||||
group: {
|
group: {
|
||||||
@@ -21,7 +21,7 @@ interface RenameGroupFormProps {
|
|||||||
export const RenameGroupForm = ({ group, disabled }: RenameGroupFormProps) => {
|
export const RenameGroupForm = ({ group, disabled }: RenameGroupFormProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { mutate, isPending } = clientApi.group.updateGroup.useMutation();
|
const { mutate, isPending } = clientApi.group.updateGroup.useMutation();
|
||||||
const form = useZodForm(validation.group.update.pick({ name: true }), {
|
const form = useZodForm(groupUpdateSchema.pick({ name: true }), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: group.name,
|
name: group.name,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { clientApi } from "@homarr/api/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { groupSettingsSchema } from "@homarr/validation/group";
|
||||||
|
|
||||||
import { BoardSelect } from "~/components/board/board-select";
|
import { BoardSelect } from "~/components/board/board-select";
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ interface GroupHomeBoardsProps {
|
|||||||
export const GroupHomeBoards = ({ homeBoardId, mobileHomeBoardId, groupId }: GroupHomeBoardsProps) => {
|
export const GroupHomeBoards = ({ homeBoardId, mobileHomeBoardId, groupId }: GroupHomeBoardsProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [availableBoards] = clientApi.board.getBoardsForGroup.useSuspenseQuery({ groupId });
|
const [availableBoards] = clientApi.board.getBoardsForGroup.useSuspenseQuery({ groupId });
|
||||||
const form = useZodForm(validation.group.settings.pick({ homeBoardId: true, mobileHomeBoardId: true }), {
|
const form = useZodForm(groupSettingsSchema.pick({ homeBoardId: true, mobileHomeBoardId: true }), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
homeBoardId,
|
homeBoardId,
|
||||||
mobileHomeBoardId,
|
mobileHomeBoardId,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useModalAction } from "@homarr/modals";
|
|||||||
import { showSuccessNotification } from "@homarr/notifications";
|
import { showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useSettings } from "@homarr/settings";
|
import { useSettings } from "@homarr/settings";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||||
import { loadWidgetDynamic, reduceWidgetOptionsWithDefaultValues, widgetImports } from "@homarr/widgets";
|
import { loadWidgetDynamic, reduceWidgetOptionsWithDefaultValues, widgetImports } from "@homarr/widgets";
|
||||||
import { WidgetError } from "@homarr/widgets/errors";
|
import { WidgetError } from "@homarr/widgets/errors";
|
||||||
import { WidgetEditModal } from "@homarr/widgets/modals";
|
import { WidgetEditModal } from "@homarr/widgets/modals";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||||
|
|
||||||
import type { CreateItemInput } from "./actions/create-item";
|
import type { CreateItemInput } from "./actions/create-item";
|
||||||
import { createItemCallback } from "./actions/create-item";
|
import { createItemCallback } from "./actions/create-item";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { clientApi } from "@homarr/api/client";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardRenameSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
interface InnerProps {
|
interface InnerProps {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -26,7 +26,7 @@ export const BoardRenameModal = createModal<InnerProps>(({ actions, innerProps }
|
|||||||
void utils.board.getHomeBoard.invalidate();
|
void utils.board.getHomeBoard.invalidate();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const form = useZodForm(validation.board.rename.omit({ id: true }), {
|
const form = useZodForm(boardRenameSchema.omit({ id: true }), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: innerProps.previousName,
|
name: innerProps.previousName,
|
||||||
},
|
},
|
||||||
@@ -66,4 +66,4 @@ export const BoardRenameModal = createModal<InnerProps>(({ actions, innerProps }
|
|||||||
defaultTitle: (t) => t("board.setting.section.dangerZone.action.rename.modal.title"),
|
defaultTitle: (t) => t("board.setting.section.dangerZone.action.rename.modal.title"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormType = Omit<z.infer<(typeof validation)["board"]["rename"]>, "id">;
|
type FormType = Omit<z.infer<typeof boardRenameSchema>, "id">;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback } from "react";
|
|||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
|
||||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||||
import type { dynamicSectionOptionsSchema } from "@homarr/validation";
|
import type { dynamicSectionOptionsSchema } from "@homarr/validation/shared";
|
||||||
|
|
||||||
import { addDynamicSectionCallback } from "./actions/add-dynamic-section";
|
import { addDynamicSectionCallback } from "./actions/add-dynamic-section";
|
||||||
import type { RemoveDynamicSectionInput } from "./actions/remove-dynamic-section";
|
import type { RemoveDynamicSectionInput } from "./actions/remove-dynamic-section";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { z } from "zod";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { dynamicSectionOptionsSchema } from "@homarr/validation";
|
import { dynamicSectionOptionsSchema } from "@homarr/validation/shared";
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
value: z.infer<typeof dynamicSectionOptionsSchema>;
|
value: z.infer<typeof dynamicSectionOptionsSchema>;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { asc, createId, eq, inArray, like } from "@homarr/db";
|
|||||||
import { apps } from "@homarr/db/schema";
|
import { apps } from "@homarr/db/schema";
|
||||||
import { selectAppSchema } from "@homarr/db/validationSchemas";
|
import { selectAppSchema } from "@homarr/db/validationSchemas";
|
||||||
import { getIconForName } from "@homarr/icons";
|
import { getIconForName } from "@homarr/icons";
|
||||||
import { validation } from "@homarr/validation";
|
import { appCreateManySchema, appEditSchema, appManageSchema } from "@homarr/validation/app";
|
||||||
|
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||||
|
|
||||||
import { convertIntersectionToZodObject } from "../schema-merger";
|
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
@@ -15,7 +16,7 @@ const defaultIcon = "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@mas
|
|||||||
|
|
||||||
export const appRouter = createTRPCRouter({
|
export const appRouter = createTRPCRouter({
|
||||||
getPaginated: protectedProcedure
|
getPaginated: protectedProcedure
|
||||||
.input(validation.common.paginated)
|
.input(paginatedSchema)
|
||||||
.output(z.object({ items: z.array(selectAppSchema), totalCount: z.number() }))
|
.output(z.object({ items: z.array(selectAppSchema), totalCount: z.number() }))
|
||||||
.meta({ openapi: { method: "GET", path: "/api/apps/paginated", tags: ["apps"], protect: true } })
|
.meta({ openapi: { method: "GET", path: "/api/apps/paginated", tags: ["apps"], protect: true } })
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
@@ -83,7 +84,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
byId: publicProcedure
|
byId: publicProcedure
|
||||||
.input(validation.common.byId)
|
.input(byIdSchema)
|
||||||
.output(selectAppSchema)
|
.output(selectAppSchema)
|
||||||
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
.meta({ openapi: { method: "GET", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
@@ -115,7 +116,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
create: permissionRequiredProcedure
|
create: permissionRequiredProcedure
|
||||||
.requiresPermission("app-create")
|
.requiresPermission("app-create")
|
||||||
.input(validation.app.manage)
|
.input(appManageSchema)
|
||||||
.output(z.object({ appId: z.string() }))
|
.output(z.object({ appId: z.string() }))
|
||||||
.meta({ openapi: { method: "POST", path: "/api/apps", tags: ["apps"], protect: true } })
|
.meta({ openapi: { method: "POST", path: "/api/apps", tags: ["apps"], protect: true } })
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
@@ -133,7 +134,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
createMany: permissionRequiredProcedure
|
createMany: permissionRequiredProcedure
|
||||||
.requiresPermission("app-create")
|
.requiresPermission("app-create")
|
||||||
.input(validation.app.createMany)
|
.input(appCreateManySchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await ctx.db.insert(apps).values(
|
await ctx.db.insert(apps).values(
|
||||||
@@ -148,7 +149,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
update: permissionRequiredProcedure
|
update: permissionRequiredProcedure
|
||||||
.requiresPermission("app-modify-all")
|
.requiresPermission("app-modify-all")
|
||||||
.input(convertIntersectionToZodObject(validation.app.edit))
|
.input(convertIntersectionToZodObject(appEditSchema))
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "PATCH", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
.meta({ openapi: { method: "PATCH", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
@@ -178,7 +179,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
.requiresPermission("app-full-all")
|
.requiresPermission("app-full-all")
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "DELETE", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
.meta({ openapi: { method: "DELETE", path: "/api/apps/{id}", tags: ["apps"], protect: true } })
|
||||||
.input(validation.common.byId)
|
.input(byIdSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await ctx.db.delete(apps).where(eq(apps.id, input.id));
|
await ctx.db.delete(apps).where(eq(apps.id, input.id));
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -37,8 +37,21 @@ import {
|
|||||||
import { importOldmarrAsync } from "@homarr/old-import";
|
import { importOldmarrAsync } from "@homarr/old-import";
|
||||||
import { importJsonFileSchema } from "@homarr/old-import/shared";
|
import { importJsonFileSchema } from "@homarr/old-import/shared";
|
||||||
import { oldmarrConfigSchema } from "@homarr/old-schema";
|
import { oldmarrConfigSchema } from "@homarr/old-schema";
|
||||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
import {
|
||||||
import { sectionSchema, sharedItemSchema, validation, zodUnionFromArray } from "@homarr/validation";
|
boardByNameSchema,
|
||||||
|
boardChangeVisibilitySchema,
|
||||||
|
boardCreateSchema,
|
||||||
|
boardDuplicateSchema,
|
||||||
|
boardRenameSchema,
|
||||||
|
boardSaveLayoutsSchema,
|
||||||
|
boardSavePartialSettingsSchema,
|
||||||
|
boardSavePermissionsSchema,
|
||||||
|
boardSaveSchema,
|
||||||
|
} from "@homarr/validation/board";
|
||||||
|
import { byIdSchema } from "@homarr/validation/common";
|
||||||
|
import { zodUnionFromArray } from "@homarr/validation/enums";
|
||||||
|
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||||
|
import { sectionSchema, sharedItemSchema } from "@homarr/validation/shared";
|
||||||
|
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
||||||
@@ -247,7 +260,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
createBoard: permissionRequiredProcedure
|
createBoard: permissionRequiredProcedure
|
||||||
.requiresPermission("board-create")
|
.requiresPermission("board-create")
|
||||||
.input(validation.board.create)
|
.input(boardCreateSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const boardId = createId();
|
const boardId = createId();
|
||||||
|
|
||||||
@@ -291,7 +304,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
duplicateBoard: permissionRequiredProcedure
|
duplicateBoard: permissionRequiredProcedure
|
||||||
.requiresPermission("board-create")
|
.requiresPermission("board-create")
|
||||||
.input(validation.board.duplicate)
|
.input(boardDuplicateSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view");
|
||||||
await noBoardWithSimilarNameAsync(ctx.db, input.name);
|
await noBoardWithSimilarNameAsync(ctx.db, input.name);
|
||||||
@@ -506,34 +519,32 @@ export const boardRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
renameBoard: protectedProcedure.input(validation.board.rename).mutation(async ({ ctx, input }) => {
|
renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||||
|
|
||||||
await noBoardWithSimilarNameAsync(ctx.db, input.name, [input.id]);
|
await noBoardWithSimilarNameAsync(ctx.db, input.name, [input.id]);
|
||||||
|
|
||||||
await ctx.db.update(boards).set({ name: input.name }).where(eq(boards.id, input.id));
|
await ctx.db.update(boards).set({ name: input.name }).where(eq(boards.id, input.id));
|
||||||
}),
|
}),
|
||||||
changeBoardVisibility: protectedProcedure
|
changeBoardVisibility: protectedProcedure.input(boardChangeVisibilitySchema).mutation(async ({ ctx, input }) => {
|
||||||
.input(validation.board.changeVisibility)
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||||
.mutation(async ({ ctx, input }) => {
|
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
|
||||||
const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board");
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
input.visibility !== "public" &&
|
input.visibility !== "public" &&
|
||||||
(boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id)
|
(boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id)
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
message: "Cannot make home board private",
|
message: "Cannot make home board private",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.db
|
await ctx.db
|
||||||
.update(boards)
|
.update(boards)
|
||||||
.set({ isPublic: input.visibility === "public" })
|
.set({ isPublic: input.visibility === "public" })
|
||||||
.where(eq(boards.id, input.id));
|
.where(eq(boards.id, input.id));
|
||||||
}),
|
}),
|
||||||
deleteBoard: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
|
deleteBoard: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||||
|
|
||||||
@@ -572,13 +583,13 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||||
}),
|
}),
|
||||||
getBoardByName: publicProcedure.input(validation.board.byName).query(async ({ input, ctx }) => {
|
getBoardByName: publicProcedure.input(boardByNameSchema).query(async ({ input, ctx }) => {
|
||||||
const boardWhere = eq(sql`UPPER(${boards.name})`, input.name.toUpperCase());
|
const boardWhere = eq(sql`UPPER(${boards.name})`, input.name.toUpperCase());
|
||||||
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
||||||
|
|
||||||
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||||
}),
|
}),
|
||||||
saveLayouts: protectedProcedure.input(validation.board.saveLayouts).mutation(async ({ ctx, input }) => {
|
saveLayouts: protectedProcedure.input(boardSaveLayoutsSchema).mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||||
|
|
||||||
const board = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
const board = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
||||||
@@ -704,7 +715,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
savePartialBoardSettings: protectedProcedure
|
savePartialBoardSettings: protectedProcedure
|
||||||
.input(validation.board.savePartialSettings.and(z.object({ id: z.string() })))
|
.input(boardSavePartialSettingsSchema.and(z.object({ id: z.string() })))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||||
|
|
||||||
@@ -738,7 +749,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
})
|
})
|
||||||
.where(eq(boards.id, input.id));
|
.where(eq(boards.id, input.id));
|
||||||
}),
|
}),
|
||||||
saveBoard: protectedProcedure.input(validation.board.save).mutation(async ({ input, ctx }) => {
|
saveBoard: protectedProcedure.input(boardSaveSchema).mutation(async ({ input, ctx }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||||
|
|
||||||
const dbBoard = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
const dbBoard = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
||||||
@@ -1154,8 +1165,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
getBoardPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||||
getBoardPermissions: protectedProcedure.input(validation.board.permissions).query(async ({ input, ctx }) => {
|
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full");
|
||||||
|
|
||||||
const dbGroupPermissions = await ctx.db.query.groupPermissions.findMany({
|
const dbGroupPermissions = await ctx.db.query.groupPermissions.findMany({
|
||||||
@@ -1226,92 +1236,86 @@ export const boardRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
saveUserBoardPermissions: protectedProcedure
|
saveUserBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
|
||||||
.input(validation.board.savePermissions)
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
|
||||||
|
|
||||||
await handleTransactionsAsync(ctx.db, {
|
await handleTransactionsAsync(ctx.db, {
|
||||||
async handleAsync(db, schema) {
|
async handleAsync(db, schema) {
|
||||||
await db.transaction(async (transaction) => {
|
await db.transaction(async (transaction) => {
|
||||||
await transaction
|
await transaction.delete(schema.boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId));
|
||||||
.delete(schema.boardUserPermissions)
|
if (input.permissions.length === 0) {
|
||||||
.where(eq(boardUserPermissions.boardId, input.entityId));
|
return;
|
||||||
if (input.permissions.length === 0) {
|
}
|
||||||
return;
|
await transaction.insert(schema.boardUserPermissions).values(
|
||||||
}
|
input.permissions.map((permission) => ({
|
||||||
await transaction.insert(schema.boardUserPermissions).values(
|
userId: permission.principalId,
|
||||||
|
permission: permission.permission,
|
||||||
|
boardId: input.entityId,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSync(db) {
|
||||||
|
db.transaction((transaction) => {
|
||||||
|
transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run();
|
||||||
|
if (input.permissions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transaction
|
||||||
|
.insert(boardUserPermissions)
|
||||||
|
.values(
|
||||||
input.permissions.map((permission) => ({
|
input.permissions.map((permission) => ({
|
||||||
userId: permission.principalId,
|
userId: permission.principalId,
|
||||||
permission: permission.permission,
|
permission: permission.permission,
|
||||||
boardId: input.entityId,
|
boardId: input.entityId,
|
||||||
})),
|
})),
|
||||||
);
|
)
|
||||||
});
|
.run();
|
||||||
},
|
});
|
||||||
handleSync(db) {
|
},
|
||||||
db.transaction((transaction) => {
|
});
|
||||||
transaction.delete(boardUserPermissions).where(eq(boardUserPermissions.boardId, input.entityId)).run();
|
}),
|
||||||
if (input.permissions.length === 0) {
|
saveGroupBoardPermissions: protectedProcedure.input(boardSavePermissionsSchema).mutation(async ({ input, ctx }) => {
|
||||||
return;
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
||||||
}
|
|
||||||
transaction
|
|
||||||
.insert(boardUserPermissions)
|
|
||||||
.values(
|
|
||||||
input.permissions.map((permission) => ({
|
|
||||||
userId: permission.principalId,
|
|
||||||
permission: permission.permission,
|
|
||||||
boardId: input.entityId,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.run();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
saveGroupBoardPermissions: protectedProcedure
|
|
||||||
.input(validation.board.savePermissions)
|
|
||||||
.mutation(async ({ input, ctx }) => {
|
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.entityId), "full");
|
|
||||||
|
|
||||||
await handleTransactionsAsync(ctx.db, {
|
await handleTransactionsAsync(ctx.db, {
|
||||||
async handleAsync(db, schema) {
|
async handleAsync(db, schema) {
|
||||||
await db.transaction(async (transaction) => {
|
await db.transaction(async (transaction) => {
|
||||||
await transaction
|
await transaction
|
||||||
.delete(schema.boardGroupPermissions)
|
.delete(schema.boardGroupPermissions)
|
||||||
.where(eq(boardGroupPermissions.boardId, input.entityId));
|
.where(eq(boardGroupPermissions.boardId, input.entityId));
|
||||||
if (input.permissions.length === 0) {
|
if (input.permissions.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await transaction.insert(schema.boardGroupPermissions).values(
|
await transaction.insert(schema.boardGroupPermissions).values(
|
||||||
|
input.permissions.map((permission) => ({
|
||||||
|
groupId: permission.principalId,
|
||||||
|
permission: permission.permission,
|
||||||
|
boardId: input.entityId,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSync(db) {
|
||||||
|
db.transaction((transaction) => {
|
||||||
|
transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run();
|
||||||
|
if (input.permissions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transaction
|
||||||
|
.insert(boardGroupPermissions)
|
||||||
|
.values(
|
||||||
input.permissions.map((permission) => ({
|
input.permissions.map((permission) => ({
|
||||||
groupId: permission.principalId,
|
groupId: permission.principalId,
|
||||||
permission: permission.permission,
|
permission: permission.permission,
|
||||||
boardId: input.entityId,
|
boardId: input.entityId,
|
||||||
})),
|
})),
|
||||||
);
|
)
|
||||||
});
|
.run();
|
||||||
},
|
});
|
||||||
handleSync(db) {
|
},
|
||||||
db.transaction((transaction) => {
|
});
|
||||||
transaction.delete(boardGroupPermissions).where(eq(boardGroupPermissions.boardId, input.entityId)).run();
|
}),
|
||||||
if (input.permissions.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
transaction
|
|
||||||
.insert(boardGroupPermissions)
|
|
||||||
.values(
|
|
||||||
input.permissions.map((permission) => ({
|
|
||||||
groupId: permission.principalId,
|
|
||||||
permission: permission.permission,
|
|
||||||
boardId: input.entityId,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.run();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
importOldmarrConfig: permissionRequiredProcedure
|
importOldmarrConfig: permissionRequiredProcedure
|
||||||
.requiresPermission("board-create")
|
.requiresPermission("board-create")
|
||||||
.input(importJsonFileSchema)
|
.input(importJsonFileSchema)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|||||||
import { zfd } from "zod-form-data";
|
import { zfd } from "zod-form-data";
|
||||||
|
|
||||||
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
|
import { addCustomRootCertificateAsync, removeCustomRootCertificateAsync } from "@homarr/certificates/server";
|
||||||
import { superRefineCertificateFile, validation } from "@homarr/validation";
|
import { certificateValidFileNameSchema, superRefineCertificateFile } from "@homarr/validation/certificates";
|
||||||
|
|
||||||
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure } from "../../trpc";
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export const certificateRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
removeCertificate: permissionRequiredProcedure
|
removeCertificate: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(z.object({ fileName: validation.certificates.validFileNameSchema }))
|
.input(z.object({ fileName: certificateValidFileNameSchema }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
await removeCustomRootCertificateAsync(input.fileName);
|
await removeCustomRootCertificateAsync(input.fileName);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ import { and, createId, eq, handleTransactionsAsync, like, not } from "@homarr/d
|
|||||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||||
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema";
|
import { groupMembers, groupPermissions, groups } from "@homarr/db/schema";
|
||||||
import { everyoneGroup } from "@homarr/definitions";
|
import { everyoneGroup } from "@homarr/definitions";
|
||||||
import { validation } from "@homarr/validation";
|
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||||
|
import {
|
||||||
|
groupCreateSchema,
|
||||||
|
groupSavePartialSettingsSchema,
|
||||||
|
groupSavePermissionsSchema,
|
||||||
|
groupSavePositionsSchema,
|
||||||
|
groupUpdateSchema,
|
||||||
|
groupUserSchema,
|
||||||
|
} from "@homarr/validation/group";
|
||||||
|
|
||||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, protectedProcedure } from "../trpc";
|
||||||
import { throwIfCredentialsDisabled } from "./invite/checks";
|
import { throwIfCredentialsDisabled } from "./invite/checks";
|
||||||
@@ -39,7 +47,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
|
|
||||||
getPaginated: permissionRequiredProcedure
|
getPaginated: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.common.paginated)
|
.input(paginatedSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined;
|
const whereQuery = input.search ? like(groups.name, `%${input.search.trim()}%`) : undefined;
|
||||||
const groupCount = await ctx.db.$count(groups, whereQuery);
|
const groupCount = await ctx.db.$count(groups, whereQuery);
|
||||||
@@ -74,7 +82,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
getById: permissionRequiredProcedure
|
getById: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.common.byId)
|
.input(byIdSchema)
|
||||||
.query(async ({ input, ctx }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
const group = await ctx.db.query.groups.findFirst({
|
const group = await ctx.db.query.groups.findFirst({
|
||||||
where: eq(groups.id, input.id),
|
where: eq(groups.id, input.id),
|
||||||
@@ -169,7 +177,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
createInitialExternalGroup: onboardingProcedure
|
createInitialExternalGroup: onboardingProcedure
|
||||||
.requiresStep("group")
|
.requiresStep("group")
|
||||||
.input(validation.group.create)
|
.input(groupCreateSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
||||||
|
|
||||||
@@ -191,7 +199,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
createGroup: permissionRequiredProcedure
|
createGroup: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.create)
|
.input(groupCreateSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
await checkSimilarNameAndThrowAsync(ctx.db, input.name);
|
||||||
|
|
||||||
@@ -209,7 +217,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
updateGroup: permissionRequiredProcedure
|
updateGroup: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.update)
|
.input(groupUpdateSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||||
@@ -225,7 +233,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
savePartialSettings: permissionRequiredProcedure
|
savePartialSettings: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.savePartialSettings)
|
.input(groupSavePartialSettingsSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||||
|
|
||||||
@@ -239,7 +247,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
savePositions: permissionRequiredProcedure
|
savePositions: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.savePositions)
|
.input(groupSavePositionsSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const positions = input.positions.map((id, index) => ({ id, position: index + 1 }));
|
const positions = input.positions.map((id, index) => ({ id, position: index + 1 }));
|
||||||
|
|
||||||
@@ -262,7 +270,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
savePermissions: permissionRequiredProcedure
|
savePermissions: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.savePermissions)
|
.input(groupSavePermissionsSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||||
|
|
||||||
@@ -277,7 +285,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
transferOwnership: permissionRequiredProcedure
|
transferOwnership: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.groupUser)
|
.input(groupUserSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||||
@@ -291,7 +299,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
deleteGroup: permissionRequiredProcedure
|
deleteGroup: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.common.byId)
|
.input(byIdSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
await throwIfGroupNotFoundAsync(ctx.db, input.id);
|
||||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
await throwIfGroupNameIsReservedAsync(ctx.db, input.id);
|
||||||
@@ -300,7 +308,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
addMember: permissionRequiredProcedure
|
addMember: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.groupUser)
|
.input(groupUserSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||||
@@ -324,7 +332,7 @@ export const groupRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
removeMember: permissionRequiredProcedure
|
removeMember: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.input(validation.group.groupUser)
|
.input(groupUserSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
await throwIfGroupNotFoundAsync(ctx.db, input.groupId);
|
||||||
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
await throwIfGroupNameIsReservedAsync(ctx.db, input.groupId);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { and, like } from "@homarr/db";
|
import { and, like } from "@homarr/db";
|
||||||
import { icons } from "@homarr/db/schema";
|
import { icons } from "@homarr/db/schema";
|
||||||
import { validation } from "@homarr/validation";
|
import { iconsFindSchema } from "@homarr/validation/icons";
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||||
|
|
||||||
export const iconsRouter = createTRPCRouter({
|
export const iconsRouter = createTRPCRouter({
|
||||||
findIcons: publicProcedure.input(validation.icons.findIcons).query(async ({ ctx, input }) => {
|
findIcons: publicProcedure.input(iconsFindSchema).query(async ({ ctx, input }) => {
|
||||||
return {
|
return {
|
||||||
icons: await ctx.db.query.iconRepositories.findMany({
|
icons: await ctx.db.query.iconRepositories.findMany({
|
||||||
with: {
|
with: {
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ import {
|
|||||||
integrationSecretKindObject,
|
integrationSecretKindObject,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import { createIntegrationAsync } from "@homarr/integrations";
|
import { createIntegrationAsync } from "@homarr/integrations";
|
||||||
import { validation } from "@homarr/validation";
|
import { byIdSchema } from "@homarr/validation/common";
|
||||||
|
import {
|
||||||
|
integrationCreateSchema,
|
||||||
|
integrationSavePermissionsSchema,
|
||||||
|
integrationUpdateSchema,
|
||||||
|
} from "@homarr/validation/integration";
|
||||||
|
|
||||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
||||||
@@ -141,7 +146,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
byId: protectedProcedure.input(validation.integration.byId).query(async ({ ctx, input }) => {
|
byId: protectedProcedure.input(byIdSchema).query(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||||
const integration = await ctx.db.query.integrations.findFirst({
|
const integration = await ctx.db.query.integrations.findFirst({
|
||||||
where: eq(integrations.id, input.id),
|
where: eq(integrations.id, input.id),
|
||||||
@@ -178,7 +183,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
create: permissionRequiredProcedure
|
create: permissionRequiredProcedure
|
||||||
.requiresPermission("integration-create")
|
.requiresPermission("integration-create")
|
||||||
.input(validation.integration.create)
|
.input(integrationCreateSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await testConnectionAsync({
|
await testConnectionAsync({
|
||||||
id: "new",
|
id: "new",
|
||||||
@@ -221,7 +226,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
update: protectedProcedure.input(validation.integration.update).mutation(async ({ ctx, input }) => {
|
update: protectedProcedure.input(integrationUpdateSchema).mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||||
|
|
||||||
const integration = await ctx.db.query.integrations.findFirst({
|
const integration = await ctx.db.query.integrations.findFirst({
|
||||||
@@ -282,7 +287,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
delete: protectedProcedure.input(validation.integration.delete).mutation(async ({ ctx, input }) => {
|
delete: protectedProcedure.input(byIdSchema).mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||||
|
|
||||||
const integration = await ctx.db.query.integrations.findFirst({
|
const integration = await ctx.db.query.integrations.findFirst({
|
||||||
@@ -298,7 +303,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await ctx.db.delete(integrations).where(eq(integrations.id, input.id));
|
await ctx.db.delete(integrations).where(eq(integrations.id, input.id));
|
||||||
}),
|
}),
|
||||||
getIntegrationPermissions: protectedProcedure.input(validation.board.permissions).query(async ({ input, ctx }) => {
|
getIntegrationPermissions: protectedProcedure.input(byIdSchema).query(async ({ input, ctx }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.id), "full");
|
||||||
|
|
||||||
const dbGroupPermissions = await ctx.db.query.groupPermissions.findMany({
|
const dbGroupPermissions = await ctx.db.query.groupPermissions.findMany({
|
||||||
@@ -370,7 +375,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
saveUserIntegrationPermissions: protectedProcedure
|
saveUserIntegrationPermissions: protectedProcedure
|
||||||
.input(validation.integration.savePermissions)
|
.input(integrationSavePermissionsSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
||||||
|
|
||||||
@@ -416,7 +421,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
saveGroupIntegrationPermissions: protectedProcedure
|
saveGroupIntegrationPermissions: protectedProcedure
|
||||||
.input(validation.integration.savePermissions)
|
.input(integrationSavePermissionsSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
await throwIfActionForbiddenAsync(ctx, eq(integrations.id, input.entityId), "full");
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,42 @@
|
|||||||
import type { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { fetchWithTimeout } from "@homarr/common";
|
import { fetchWithTimeout } from "@homarr/common";
|
||||||
import { validation } from "@homarr/validation";
|
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from "../trpc";
|
import { createTRPCRouter, publicProcedure } from "../trpc";
|
||||||
|
|
||||||
|
const citySchema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
country: z.string().optional(),
|
||||||
|
country_code: z.string().optional(),
|
||||||
|
latitude: z.number(),
|
||||||
|
longitude: z.number(),
|
||||||
|
population: z.number().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const locationSearchCityInput = z.object({
|
||||||
|
query: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const locationSearchCityOutput = z
|
||||||
|
.object({
|
||||||
|
results: z.array(citySchema),
|
||||||
|
})
|
||||||
|
.or(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
generationtime_ms: z.number(),
|
||||||
|
})
|
||||||
|
.refine((data) => Object.keys(data).length === 1, { message: "Invalid response" })
|
||||||
|
.transform(() => ({ results: [] })), // We fallback to empty array if no results
|
||||||
|
);
|
||||||
|
|
||||||
export const locationRouter = createTRPCRouter({
|
export const locationRouter = createTRPCRouter({
|
||||||
searchCity: publicProcedure
|
searchCity: publicProcedure
|
||||||
.input(validation.location.searchCity.input)
|
.input(locationSearchCityInput)
|
||||||
.output(validation.location.searchCity.output)
|
.output(locationSearchCityOutput)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const res = await fetchWithTimeout(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
const res = await fetchWithTimeout(`https://geocoding-api.open-meteo.com/v1/search?name=${input.query}`);
|
||||||
return (await res.json()) as z.infer<typeof validation.location.searchCity.output>;
|
return (await res.json()) as z.infer<typeof locationSearchCityOutput>;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import type { InferInsertModel } from "@homarr/db";
|
|||||||
import { and, createId, desc, eq, like } from "@homarr/db";
|
import { and, createId, desc, eq, like } from "@homarr/db";
|
||||||
import { iconRepositories, icons, medias } from "@homarr/db/schema";
|
import { iconRepositories, icons, medias } from "@homarr/db/schema";
|
||||||
import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local";
|
import { createLocalImageUrl, LOCAL_ICON_REPOSITORY_SLUG, mapMediaToIcon } from "@homarr/icons/local";
|
||||||
import { validation } from "@homarr/validation";
|
import { byIdSchema, paginatedSchema } from "@homarr/validation/common";
|
||||||
|
import { mediaUploadSchema } from "@homarr/validation/media";
|
||||||
|
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure } from "../../trpc";
|
||||||
|
|
||||||
export const mediaRouter = createTRPCRouter({
|
export const mediaRouter = createTRPCRouter({
|
||||||
getPaginated: protectedProcedure
|
getPaginated: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
validation.common.paginated.and(
|
paginatedSchema.and(
|
||||||
z.object({ includeFromAllUsers: z.boolean().default(false), search: z.string().trim().default("") }),
|
z.object({ includeFromAllUsers: z.boolean().default(false), search: z.string().trim().default("") }),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -51,7 +52,7 @@ export const mediaRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
uploadMedia: permissionRequiredProcedure
|
uploadMedia: permissionRequiredProcedure
|
||||||
.requiresPermission("media-upload")
|
.requiresPermission("media-upload")
|
||||||
.input(validation.media.uploadMedia)
|
.input(mediaUploadSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const content = Buffer.from(await input.file.arrayBuffer());
|
const content = Buffer.from(await input.file.arrayBuffer());
|
||||||
const id = createId();
|
const id = createId();
|
||||||
@@ -82,7 +83,7 @@ export const mediaRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return id;
|
return id;
|
||||||
}),
|
}),
|
||||||
deleteMedia: protectedProcedure.input(validation.common.byId).mutation(async ({ ctx, input }) => {
|
deleteMedia: protectedProcedure.input(byIdSchema).mutation(async ({ ctx, input }) => {
|
||||||
const dbMedia = await ctx.db.query.medias.findFirst({
|
const dbMedia = await ctx.db.query.medias.findFirst({
|
||||||
where: eq(medias.id, input.id),
|
where: eq(medias.id, input.id),
|
||||||
columns: {
|
columns: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { onboarding } from "@homarr/db/schema";
|
import { onboarding } from "@homarr/db/schema";
|
||||||
import { onboardingSteps } from "@homarr/definitions";
|
import { onboardingSteps } from "@homarr/definitions";
|
||||||
import { zodEnumFromArray } from "@homarr/validation";
|
import { zodEnumFromArray } from "@homarr/validation/enums";
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||||
import { getOnboardingOrFallbackAsync, nextOnboardingStepAsync } from "./onboard-queries";
|
import { getOnboardingOrFallbackAsync, nextOnboardingStepAsync } from "./onboard-queries";
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import { asc, createId, eq, like } from "@homarr/db";
|
|||||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||||
import { searchEngines, users } from "@homarr/db/schema";
|
import { searchEngines, users } from "@homarr/db/schema";
|
||||||
import { createIntegrationAsync } from "@homarr/integrations";
|
import { createIntegrationAsync } from "@homarr/integrations";
|
||||||
import { validation } from "@homarr/validation";
|
import { byIdSchema, paginatedSchema, searchSchema } from "@homarr/validation/common";
|
||||||
|
import { searchEngineEditSchema, searchEngineManageSchema } from "@homarr/validation/search-engine";
|
||||||
|
import { mediaRequestOptionsSchema, mediaRequestRequestSchema } from "@homarr/validation/widgets/media-request";
|
||||||
|
|
||||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../../trpc";
|
||||||
|
|
||||||
export const searchEngineRouter = createTRPCRouter({
|
export const searchEngineRouter = createTRPCRouter({
|
||||||
getPaginated: protectedProcedure.input(validation.common.paginated).query(async ({ input, ctx }) => {
|
getPaginated: protectedProcedure.input(paginatedSchema).query(async ({ input, ctx }) => {
|
||||||
const whereQuery = input.search ? like(searchEngines.name, `%${input.search.trim()}%`) : undefined;
|
const whereQuery = input.search ? like(searchEngines.name, `%${input.search.trim()}%`) : undefined;
|
||||||
const searchEngineCount = await ctx.db.$count(searchEngines, whereQuery);
|
const searchEngineCount = await ctx.db.$count(searchEngines, whereQuery);
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ export const searchEngineRouter = createTRPCRouter({
|
|||||||
.then((engines) => engines.map((engine) => ({ value: engine.id, label: engine.name })));
|
.then((engines) => engines.map((engine) => ({ value: engine.id, label: engine.name })));
|
||||||
}),
|
}),
|
||||||
|
|
||||||
byId: protectedProcedure.input(validation.common.byId).query(async ({ ctx, input }) => {
|
byId: protectedProcedure.input(byIdSchema).query(async ({ ctx, input }) => {
|
||||||
const searchEngine = await ctx.db.query.searchEngines.findFirst({
|
const searchEngine = await ctx.db.query.searchEngines.findFirst({
|
||||||
where: eq(searchEngines.id, input.id),
|
where: eq(searchEngines.id, input.id),
|
||||||
});
|
});
|
||||||
@@ -115,7 +117,7 @@ export const searchEngineRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
search: protectedProcedure.input(validation.common.search).query(async ({ ctx, input }) => {
|
search: protectedProcedure.input(searchSchema).query(async ({ ctx, input }) => {
|
||||||
return await ctx.db.query.searchEngines.findMany({
|
return await ctx.db.query.searchEngines.findMany({
|
||||||
where: like(searchEngines.short, `${input.query.toLowerCase().trim()}%`),
|
where: like(searchEngines.short, `${input.query.toLowerCase().trim()}%`),
|
||||||
with: {
|
with: {
|
||||||
@@ -132,21 +134,21 @@ export const searchEngineRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
getMediaRequestOptions: protectedProcedure
|
getMediaRequestOptions: protectedProcedure
|
||||||
.unstable_concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
|
.unstable_concat(createOneIntegrationMiddleware("query", "jellyseerr", "overseerr"))
|
||||||
.input(validation.common.mediaRequestOptions)
|
.input(mediaRequestOptionsSchema)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const integration = await createIntegrationAsync(ctx.integration);
|
const integration = await createIntegrationAsync(ctx.integration);
|
||||||
return await integration.getSeriesInformationAsync(input.mediaType, input.mediaId);
|
return await integration.getSeriesInformationAsync(input.mediaType, input.mediaId);
|
||||||
}),
|
}),
|
||||||
requestMedia: protectedProcedure
|
requestMedia: protectedProcedure
|
||||||
.unstable_concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
|
.unstable_concat(createOneIntegrationMiddleware("interact", "jellyseerr", "overseerr"))
|
||||||
.input(validation.common.requestMedia)
|
.input(mediaRequestRequestSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const integration = await createIntegrationAsync(ctx.integration);
|
const integration = await createIntegrationAsync(ctx.integration);
|
||||||
return await integration.requestMediaAsync(input.mediaType, input.mediaId, input.seasons);
|
return await integration.requestMediaAsync(input.mediaType, input.mediaId, input.seasons);
|
||||||
}),
|
}),
|
||||||
create: permissionRequiredProcedure
|
create: permissionRequiredProcedure
|
||||||
.requiresPermission("search-engine-create")
|
.requiresPermission("search-engine-create")
|
||||||
.input(validation.searchEngine.manage)
|
.input(searchEngineManageSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await ctx.db.insert(searchEngines).values({
|
await ctx.db.insert(searchEngines).values({
|
||||||
id: createId(),
|
id: createId(),
|
||||||
@@ -161,7 +163,7 @@ export const searchEngineRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
update: permissionRequiredProcedure
|
update: permissionRequiredProcedure
|
||||||
.requiresPermission("search-engine-modify-all")
|
.requiresPermission("search-engine-modify-all")
|
||||||
.input(validation.searchEngine.edit)
|
.input(searchEngineEditSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const searchEngine = await ctx.db.query.searchEngines.findFirst({
|
const searchEngine = await ctx.db.query.searchEngines.findFirst({
|
||||||
where: eq(searchEngines.id, input.id),
|
where: eq(searchEngines.id, input.id),
|
||||||
@@ -188,7 +190,7 @@ export const searchEngineRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
delete: permissionRequiredProcedure
|
delete: permissionRequiredProcedure
|
||||||
.requiresPermission("search-engine-full-all")
|
.requiresPermission("search-engine-full-all")
|
||||||
.input(validation.common.byId)
|
.input(byIdSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await ctx.db
|
await ctx.db
|
||||||
.update(users)
|
.update(users)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|||||||
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||||
import type { ServerSettings } from "@homarr/server-settings";
|
import type { ServerSettings } from "@homarr/server-settings";
|
||||||
import { defaultServerSettingsKeys } from "@homarr/server-settings";
|
import { defaultServerSettingsKeys } from "@homarr/server-settings";
|
||||||
import { validation } from "@homarr/validation";
|
import { settingsInitSchema } from "@homarr/validation/settings";
|
||||||
|
|
||||||
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc";
|
||||||
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
import { nextOnboardingStepAsync } from "./onboard/onboard-queries";
|
||||||
@@ -32,7 +32,7 @@ export const serverSettingsRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
initSettings: onboardingProcedure
|
initSettings: onboardingProcedure
|
||||||
.requiresStep("settings")
|
.requiresStep("settings")
|
||||||
.input(validation.settings.init)
|
.input(settingsInitSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
await updateServerSettingByKeyAsync(ctx.db, "analytics", input.analytics);
|
await updateServerSettingByKeyAsync(ctx.db, "analytics", input.analytics);
|
||||||
await updateServerSettingByKeyAsync(ctx.db, "crawlingAndIndexing", input.crawlingAndIndexing);
|
await updateServerSettingByKeyAsync(ctx.db, "crawlingAndIndexing", input.crawlingAndIndexing);
|
||||||
|
|||||||
@@ -10,7 +10,20 @@ import { selectUserSchema } from "@homarr/db/validationSchemas";
|
|||||||
import { credentialsAdminGroup } from "@homarr/definitions";
|
import { credentialsAdminGroup } from "@homarr/definitions";
|
||||||
import type { SupportedAuthProvider } from "@homarr/definitions";
|
import type { SupportedAuthProvider } from "@homarr/definitions";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import { validation } from "@homarr/validation";
|
import { byIdSchema } from "@homarr/validation/common";
|
||||||
|
import type { userBaseCreateSchema } from "@homarr/validation/user";
|
||||||
|
import {
|
||||||
|
userChangeColorSchemeSchema,
|
||||||
|
userChangeHomeBoardsSchema,
|
||||||
|
userChangePasswordApiSchema,
|
||||||
|
userChangeSearchPreferencesSchema,
|
||||||
|
userCreateSchema,
|
||||||
|
userEditProfileSchema,
|
||||||
|
userFirstDayOfWeekSchema,
|
||||||
|
userInitSchema,
|
||||||
|
userPingIconsEnabledSchema,
|
||||||
|
userRegistrationApiSchema,
|
||||||
|
} from "@homarr/validation/user";
|
||||||
|
|
||||||
import { convertIntersectionToZodObject } from "../schema-merger";
|
import { convertIntersectionToZodObject } from "../schema-merger";
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +41,7 @@ import { changeSearchPreferencesAsync, changeSearchPreferencesInputSchema } from
|
|||||||
export const userRouter = createTRPCRouter({
|
export const userRouter = createTRPCRouter({
|
||||||
initUser: onboardingProcedure
|
initUser: onboardingProcedure
|
||||||
.requiresStep("user")
|
.requiresStep("user")
|
||||||
.input(validation.user.init)
|
.input(userInitSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
throwIfCredentialsDisabled();
|
throwIfCredentialsDisabled();
|
||||||
|
|
||||||
@@ -52,7 +65,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
await nextOnboardingStepAsync(ctx.db, undefined);
|
await nextOnboardingStepAsync(ctx.db, undefined);
|
||||||
}),
|
}),
|
||||||
register: publicProcedure
|
register: publicProcedure
|
||||||
.input(validation.user.registrationApi)
|
.input(userRegistrationApiSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
throwIfCredentialsDisabled();
|
throwIfCredentialsDisabled();
|
||||||
@@ -82,7 +95,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
create: permissionRequiredProcedure
|
create: permissionRequiredProcedure
|
||||||
.requiresPermission("admin")
|
.requiresPermission("admin")
|
||||||
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
|
.meta({ openapi: { method: "POST", path: "/api/users", tags: ["users"], protect: true } })
|
||||||
.input(validation.user.create)
|
.input(userCreateSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
throwIfCredentialsDisabled();
|
throwIfCredentialsDisabled();
|
||||||
@@ -259,7 +272,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
return user;
|
return user;
|
||||||
}),
|
}),
|
||||||
editProfile: protectedProcedure
|
editProfile: protectedProcedure
|
||||||
.input(validation.user.editProfile)
|
.input(userEditProfileSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "PUT", path: "/api/users/profile", tags: ["users"], protect: true } })
|
.meta({ openapi: { method: "PUT", path: "/api/users/profile", tags: ["users"], protect: true } })
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@@ -318,7 +331,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
await ctx.db.delete(users).where(eq(users.id, input.userId));
|
await ctx.db.delete(users).where(eq(users.id, input.userId));
|
||||||
}),
|
}),
|
||||||
changePassword: protectedProcedure
|
changePassword: protectedProcedure
|
||||||
.input(validation.user.changePasswordApi)
|
.input(userChangePasswordApiSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } })
|
.meta({ openapi: { method: "PATCH", path: "/api/users/{userId}/changePassword", tags: ["users"], protect: true } })
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
@@ -384,7 +397,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
.where(eq(users.id, input.userId));
|
.where(eq(users.id, input.userId));
|
||||||
}),
|
}),
|
||||||
changeHomeBoards: protectedProcedure
|
changeHomeBoards: protectedProcedure
|
||||||
.input(convertIntersectionToZodObject(validation.user.changeHomeBoards.and(z.object({ userId: z.string() }))))
|
.input(convertIntersectionToZodObject(userChangeHomeBoardsSchema.and(z.object({ userId: z.string() }))))
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "PATCH", path: "/api/users/changeHome", tags: ["users"], protect: true } })
|
.meta({ openapi: { method: "PATCH", path: "/api/users/changeHome", tags: ["users"], protect: true } })
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@@ -430,7 +443,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
changeDefaultSearchEngine: protectedProcedure
|
changeDefaultSearchEngine: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
convertIntersectionToZodObject(
|
convertIntersectionToZodObject(
|
||||||
validation.user.changeSearchPreferences.omit({ openInNewTab: true }).and(z.object({ userId: z.string() })),
|
userChangeSearchPreferencesSchema.omit({ openInNewTab: true }).and(z.object({ userId: z.string() })),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
@@ -457,7 +470,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
await changeSearchPreferencesAsync(ctx.db, ctx.session, input);
|
await changeSearchPreferencesAsync(ctx.db, ctx.session, input);
|
||||||
}),
|
}),
|
||||||
changeColorScheme: protectedProcedure
|
changeColorScheme: protectedProcedure
|
||||||
.input(validation.user.changeColorScheme)
|
.input(userChangeColorSchemeSchema)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "PATCH", path: "/api/users/changeScheme", tags: ["users"], protect: true } })
|
.meta({ openapi: { method: "PATCH", path: "/api/users/changeScheme", tags: ["users"], protect: true } })
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@@ -469,7 +482,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
.where(eq(users.id, ctx.session.user.id));
|
.where(eq(users.id, ctx.session.user.id));
|
||||||
}),
|
}),
|
||||||
changePingIconsEnabled: protectedProcedure
|
changePingIconsEnabled: protectedProcedure
|
||||||
.input(validation.user.pingIconsEnabled.and(validation.common.byId))
|
.input(userPingIconsEnabledSchema.and(byIdSchema))
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
// Only admins can change other users ping icons enabled
|
// Only admins can change other users ping icons enabled
|
||||||
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
if (!ctx.session.user.permissions.includes("admin") && ctx.session.user.id !== input.id) {
|
||||||
@@ -487,7 +500,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
.where(eq(users.id, ctx.session.user.id));
|
.where(eq(users.id, ctx.session.user.id));
|
||||||
}),
|
}),
|
||||||
changeFirstDayOfWeek: protectedProcedure
|
changeFirstDayOfWeek: protectedProcedure
|
||||||
.input(convertIntersectionToZodObject(validation.user.firstDayOfWeek.and(validation.common.byId)))
|
.input(convertIntersectionToZodObject(userFirstDayOfWeekSchema.and(byIdSchema)))
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.meta({ openapi: { method: "PATCH", path: "/api/users/firstDayOfWeek", tags: ["users"], protect: true } })
|
.meta({ openapi: { method: "PATCH", path: "/api/users/firstDayOfWeek", tags: ["users"], protect: true } })
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@@ -522,7 +535,7 @@ export const userRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof validation.user.baseCreate>, "groupIds">) => {
|
const createUserAsync = async (db: Database, input: Omit<z.infer<typeof userBaseCreateSchema>, "groupIds">) => {
|
||||||
const salt = await createSaltAsync();
|
const salt = await createSaltAsync();
|
||||||
const hashedPassword = await hashPasswordAsync(input.password, salt);
|
const hashedPassword = await hashPasswordAsync(input.password, salt);
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import type { Modify } from "@homarr/common/types";
|
|||||||
import { eq } from "@homarr/db";
|
import { eq } from "@homarr/db";
|
||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
import { users } from "@homarr/db/schema";
|
import { users } from "@homarr/db/schema";
|
||||||
import { validation } from "@homarr/validation";
|
import { userChangeSearchPreferencesSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
export const changeSearchPreferencesInputSchema = validation.user.changeSearchPreferences.and(
|
export const changeSearchPreferencesInputSchema = userChangeSearchPreferencesSchema.and(
|
||||||
z.object({ userId: z.string() }),
|
z.object({ userId: z.string() }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
import { getIntegrationKindsByCategory } from "@homarr/definitions";
|
||||||
import { mediaTranscodingRequestHandler } from "@homarr/request-handler/media-transcoding";
|
import { mediaTranscodingRequestHandler } from "@homarr/request-handler/media-transcoding";
|
||||||
import { validation } from "@homarr/validation";
|
import { paginatedSchema } from "@homarr/validation/common";
|
||||||
|
|
||||||
import type { IntegrationAction } from "../../middlewares/integration";
|
import type { IntegrationAction } from "../../middlewares/integration";
|
||||||
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
import { createOneIntegrationMiddleware } from "../../middlewares/integration";
|
||||||
@@ -12,7 +12,7 @@ const createIndexerManagerIntegrationMiddleware = (action: IntegrationAction) =>
|
|||||||
export const mediaTranscodingRouter = createTRPCRouter({
|
export const mediaTranscodingRouter = createTRPCRouter({
|
||||||
getDataAsync: publicProcedure
|
getDataAsync: publicProcedure
|
||||||
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
|
.unstable_concat(createIndexerManagerIntegrationMiddleware("query"))
|
||||||
.input(validation.common.paginated.pick({ page: true, pageSize: true }))
|
.input(paginatedSchema.pick({ page: true, pageSize: true }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
|
const innerHandler = mediaTranscodingRequestHandler.handler(ctx.integration, {
|
||||||
pageOffset: input.page,
|
pageOffset: input.page,
|
||||||
|
|||||||
@@ -1,15 +1,39 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { fetchWithTimeout } from "@homarr/common";
|
import { fetchWithTimeout } from "@homarr/common";
|
||||||
import { validation } from "@homarr/validation";
|
|
||||||
|
|
||||||
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
import { createTRPCRouter, publicProcedure } from "../../trpc";
|
||||||
|
|
||||||
|
const atLocationInput = z.object({
|
||||||
|
longitude: z.number(),
|
||||||
|
latitude: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const atLocationOutput = z.object({
|
||||||
|
current_weather: z.object({
|
||||||
|
weathercode: z.number(),
|
||||||
|
temperature: z.number(),
|
||||||
|
windspeed: z.number(),
|
||||||
|
}),
|
||||||
|
daily: z.object({
|
||||||
|
time: z.array(z.string()),
|
||||||
|
weathercode: z.array(z.number()),
|
||||||
|
temperature_2m_max: z.array(z.number()),
|
||||||
|
temperature_2m_min: z.array(z.number()),
|
||||||
|
sunrise: z.array(z.string()),
|
||||||
|
sunset: z.array(z.string()),
|
||||||
|
wind_speed_10m_max: z.array(z.number()),
|
||||||
|
wind_gusts_10m_max: z.array(z.number()),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const weatherRouter = createTRPCRouter({
|
export const weatherRouter = createTRPCRouter({
|
||||||
atLocation: publicProcedure.input(validation.widget.weather.atLocationInput).query(async ({ input }) => {
|
atLocation: publicProcedure.input(atLocationInput).query(async ({ input }) => {
|
||||||
const res = await fetchWithTimeout(
|
const res = await fetchWithTimeout(
|
||||||
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
`https://api.open-meteo.com/v1/forecast?latitude=${input.latitude}&longitude=${input.longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max¤t_weather=true&timezone=auto`,
|
||||||
);
|
);
|
||||||
const json: unknown = await res.json();
|
const json: unknown = await res.json();
|
||||||
const weather = await validation.widget.weather.atLocationOutput.parseAsync(json);
|
const weather = await atLocationOutput.parseAsync(json);
|
||||||
return {
|
return {
|
||||||
current: weather.current_weather,
|
current: weather.current_weather,
|
||||||
daily: weather.daily.time.map((value, index) => {
|
daily: weather.daily.time.map((value, index) => {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import type { Database } from "@homarr/db";
|
|||||||
import { and, eq } from "@homarr/db";
|
import { and, eq } from "@homarr/db";
|
||||||
import { users } from "@homarr/db/schema";
|
import { users } from "@homarr/db/schema";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { userSignInSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
export const authorizeWithBasicCredentialsAsync = async (
|
export const authorizeWithBasicCredentialsAsync = async (
|
||||||
db: Database,
|
db: Database,
|
||||||
credentials: z.infer<typeof validation.user.signIn>,
|
credentials: z.infer<typeof userSignInSchema>,
|
||||||
) => {
|
) => {
|
||||||
const user = await db.query.users.findFirst({
|
const user = await db.query.users.findFirst({
|
||||||
where: and(eq(users.name, credentials.name.toLowerCase()), eq(users.provider, "credentials")),
|
where: and(eq(users.name, credentials.name.toLowerCase()), eq(users.provider, "credentials")),
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import type { Database, InferInsertModel } from "@homarr/db";
|
|||||||
import { and, createId, eq } from "@homarr/db";
|
import { and, createId, eq } from "@homarr/db";
|
||||||
import { users } from "@homarr/db/schema";
|
import { users } from "@homarr/db/schema";
|
||||||
import { logger } from "@homarr/log";
|
import { logger } from "@homarr/log";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { userSignInSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
import { env } from "../../../env";
|
import { env } from "../../../env";
|
||||||
import { LdapClient } from "../ldap-client";
|
import { LdapClient } from "../ldap-client";
|
||||||
|
|
||||||
export const authorizeWithLdapCredentialsAsync = async (
|
export const authorizeWithLdapCredentialsAsync = async (
|
||||||
db: Database,
|
db: Database,
|
||||||
credentials: z.infer<typeof validation.user.signIn>,
|
credentials: z.infer<typeof userSignInSchema>,
|
||||||
) => {
|
) => {
|
||||||
logger.info(`user ${credentials.name} is trying to log in using LDAP. Connecting to LDAP server...`);
|
logger.info(`user ${credentials.name} is trying to log in using LDAP. Connecting to LDAP server...`);
|
||||||
const client = new LdapClient();
|
const client = new LdapClient();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type Credentials from "@auth/core/providers/credentials";
|
import type Credentials from "@auth/core/providers/credentials";
|
||||||
|
|
||||||
import type { Database } from "@homarr/db";
|
import type { Database } from "@homarr/db";
|
||||||
import { validation } from "@homarr/validation";
|
import { userSignInSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
import { authorizeWithBasicCredentialsAsync } from "./authorization/basic-authorization";
|
import { authorizeWithBasicCredentialsAsync } from "./authorization/basic-authorization";
|
||||||
import { authorizeWithLdapCredentialsAsync } from "./authorization/ldap-authorization";
|
import { authorizeWithLdapCredentialsAsync } from "./authorization/ldap-authorization";
|
||||||
@@ -15,7 +15,7 @@ export const createCredentialsConfiguration = (db: Database) =>
|
|||||||
name: "Credentials",
|
name: "Credentials",
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
const data = await validation.user.signIn.parseAsync(credentials);
|
const data = await userSignInSchema.parseAsync(credentials);
|
||||||
|
|
||||||
return await authorizeWithBasicCredentialsAsync(db, data);
|
return await authorizeWithBasicCredentialsAsync(db, data);
|
||||||
},
|
},
|
||||||
@@ -28,7 +28,7 @@ export const createLdapConfiguration = (db: Database) =>
|
|||||||
name: "Ldap",
|
name: "Ldap",
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
const data = await validation.user.signIn.parseAsync(credentials);
|
const data = await userSignInSchema.parseAsync(credentials);
|
||||||
return await authorizeWithLdapCredentialsAsync(db, data).catch(() => null);
|
return await authorizeWithLdapCredentialsAsync(db, data).catch(() => null);
|
||||||
},
|
},
|
||||||
}) satisfies CredentialsConfiguration;
|
}) satisfies CredentialsConfiguration;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { generateSecureRandomToken } from "@homarr/common/server";
|
|||||||
import { and, count, createId, db, eq } from "@homarr/db";
|
import { and, count, createId, db, eq } from "@homarr/db";
|
||||||
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
import { getMaxGroupPositionAsync } from "@homarr/db/queries";
|
||||||
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
|
import { groupMembers, groupPermissions, groups, users } from "@homarr/db/schema";
|
||||||
import { usernameSchema } from "@homarr/validation";
|
import { usernameSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
export const recreateAdmin = command({
|
export const recreateAdmin = command({
|
||||||
name: "recreate-admin",
|
name: "recreate-admin",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { objectKeys } from "@homarr/common";
|
import { objectKeys } from "@homarr/common";
|
||||||
import type { JobGroupKeys } from "@homarr/cron-jobs";
|
import type { JobGroupKeys } from "@homarr/cron-jobs";
|
||||||
import { createSubPubChannel } from "@homarr/redis";
|
import { createSubPubChannel } from "@homarr/redis";
|
||||||
import { zodEnumFromArray } from "@homarr/validation";
|
import { zodEnumFromArray } from "@homarr/validation/enums";
|
||||||
|
|
||||||
export const cronJobRunnerChannel = createSubPubChannel<JobGroupKeys>("cron-job-runner", { persist: false });
|
export const cronJobRunnerChannel = createSubPubChannel<JobGroupKeys>("cron-job-runner", { persist: false });
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|||||||
import type { AnyZodObject, ZodDiscriminatedUnion, ZodEffects, ZodIntersection } from "zod";
|
import type { AnyZodObject, ZodDiscriminatedUnion, ZodEffects, ZodIntersection } from "zod";
|
||||||
|
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { zodErrorMap } from "@homarr/validation/form";
|
import { zodErrorMap } from "@homarr/validation/form/i18n";
|
||||||
|
|
||||||
export const useZodForm = <
|
export const useZodForm = <
|
||||||
TSchema extends
|
TSchema extends
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { clientApi } from "@homarr/api/client";
|
|||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { appManageSchema } from "@homarr/validation/app";
|
||||||
|
|
||||||
import { AppForm } from "./_form";
|
import { AppForm } from "./_form";
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export const AppNewForm = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(values: z.infer<typeof validation.app.manage>, redirect: boolean, afterSuccess?: () => void) => {
|
(values: z.infer<typeof appManageSchema>, redirect: boolean, afterSuccess?: () => void) => {
|
||||||
mutate(values, {
|
mutate(values, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
showSuccessNotification({
|
showSuccessNotification({
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import type { z } from "zod";
|
|||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { appManageSchema } from "@homarr/validation/app";
|
||||||
|
|
||||||
import { IconPicker } from "../icon-picker/icon-picker";
|
import { IconPicker } from "../icon-picker/icon-picker";
|
||||||
import { findBestIconMatch } from "./icon-matcher";
|
import { findBestIconMatch } from "./icon-matcher";
|
||||||
|
|
||||||
type FormType = z.infer<typeof validation.app.manage>;
|
type FormType = z.infer<typeof appManageSchema>;
|
||||||
|
|
||||||
interface AppFormProps {
|
interface AppFormProps {
|
||||||
showBackToOverview: boolean;
|
showBackToOverview: boolean;
|
||||||
@@ -37,7 +37,7 @@ export const AppForm = ({
|
|||||||
}: AppFormProps) => {
|
}: AppFormProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const form = useZodForm(validation.app.manage, {
|
const form = useZodForm(appManageSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: initialValues?.name ?? "",
|
name: initialValues?.name ?? "",
|
||||||
description: initialValues?.description ?? "",
|
description: initialValues?.description ?? "",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { clientApi } from "@homarr/api/client";
|
|||||||
import type { MaybePromise } from "@homarr/common/types";
|
import type { MaybePromise } from "@homarr/common/types";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { supportedMediaUploadFormats } from "@homarr/validation";
|
import { supportedMediaUploadFormats } from "@homarr/validation/media";
|
||||||
|
|
||||||
interface UploadMediaProps {
|
interface UploadMediaProps {
|
||||||
children: (props: { onClick: () => void; loading: boolean }) => JSX.Element;
|
children: (props: { onClick: () => void; loading: boolean }) => JSX.Element;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { AppForm } from "@homarr/forms-collection";
|
|||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import type { validation } from "@homarr/validation";
|
import type { appManageSchema } from "@homarr/validation/app";
|
||||||
|
|
||||||
interface QuickAddAppModalProps {
|
interface QuickAddAppModalProps {
|
||||||
onClose: (createdAppId: string) => Promise<void>;
|
onClose: (createdAppId: string) => Promise<void>;
|
||||||
@@ -24,7 +24,7 @@ export const QuickAddAppModal = createModal<QuickAddAppModalProps>(({ actions, i
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (values: z.infer<typeof validation.app.manage>) => {
|
const handleSubmit = (values: z.infer<typeof appManageSchema>) => {
|
||||||
mutate(values, {
|
mutate(values, {
|
||||||
async onSuccess({ appId }) {
|
async onSuccess({ appId }) {
|
||||||
showSuccessNotification({
|
showSuccessNotification({
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardColumnCountSchema, boardCreateSchema, boardNameSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
export const AddBoardModal = createModal(({ actions }) => {
|
export const AddBoardModal = createModal(({ actions }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const form = useZodForm(validation.board.create, {
|
const form = useZodForm(boardCreateSchema, {
|
||||||
mode: "controlled",
|
mode: "controlled",
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -28,7 +28,7 @@ export const AddBoardModal = createModal(({ actions }) => {
|
|||||||
|
|
||||||
const boardNameStatus = useBoardNameStatus(form.values.name);
|
const boardNameStatus = useBoardNameStatus(form.values.name);
|
||||||
|
|
||||||
const columnCountChecks = validation.board.create.shape.columnCount._def.checks;
|
const columnCountChecks = boardColumnCountSchema._def.checks;
|
||||||
const minColumnCount = columnCountChecks.find((check) => check.kind === "min")?.value;
|
const minColumnCount = columnCountChecks.find((check) => check.kind === "min")?.value;
|
||||||
const maxColumnCount = columnCountChecks.find((check) => check.kind === "max")?.value;
|
const maxColumnCount = columnCountChecks.find((check) => check.kind === "max")?.value;
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ export const useBoardNameStatus = (name: string) => {
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [debouncedName] = useDebouncedValue(name, 250);
|
const [debouncedName] = useDebouncedValue(name, 250);
|
||||||
const { data: boardExists, isLoading } = clientApi.board.exists.useQuery(debouncedName, {
|
const { data: boardExists, isLoading } = clientApi.board.exists.useQuery(debouncedName, {
|
||||||
enabled: validation.board.create.shape.name.safeParse(debouncedName).success,
|
enabled: boardNameSchema.safeParse(debouncedName).success,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { MaybePromise } from "@homarr/common/types";
|
|||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { boardDuplicateSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
import { createModal } from "../../../modals/src/creator";
|
import { createModal } from "../../../modals/src/creator";
|
||||||
import { useBoardNameStatus } from "./add-board-modal";
|
import { useBoardNameStatus } from "./add-board-modal";
|
||||||
@@ -20,7 +20,7 @@ interface InnerProps {
|
|||||||
|
|
||||||
export const DuplicateBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
export const DuplicateBoardModal = createModal<InnerProps>(({ actions, innerProps }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const form = useZodForm(validation.board.duplicate.omit({ id: true }), {
|
const form = useZodForm(boardDuplicateSchema.omit({ id: true }), {
|
||||||
mode: "controlled",
|
mode: "controlled",
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: innerProps.board.name,
|
name: innerProps.board.name,
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { useZodForm } from "@homarr/form";
|
|||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { validation } from "@homarr/validation";
|
import { groupCreateSchema } from "@homarr/validation/group";
|
||||||
|
|
||||||
export const AddGroupModal = createModal<void>(({ actions }) => {
|
export const AddGroupModal = createModal<void>(({ actions }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { mutate, isPending } = clientApi.group.createGroup.useMutation();
|
const { mutate, isPending } = clientApi.group.createGroup.useMutation();
|
||||||
const form = useZodForm(validation.group.create, {
|
const form = useZodForm(groupCreateSchema, {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
name: "",
|
name: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zfd } from "zod-form-data";
|
import { zfd } from "zod-form-data";
|
||||||
|
|
||||||
import { validation } from "@homarr/validation";
|
import { boardNameSchema } from "@homarr/validation/board";
|
||||||
import { createCustomErrorParams } from "@homarr/validation/form";
|
import { createCustomErrorParams } from "@homarr/validation/form/i18n";
|
||||||
|
|
||||||
export const sidebarBehaviours = ["remove-items", "last-section"] as const;
|
export const sidebarBehaviours = ["remove-items", "last-section"] as const;
|
||||||
export const defaultSidebarBehaviour = "last-section";
|
export const defaultSidebarBehaviour = "last-section";
|
||||||
export type SidebarBehaviour = (typeof sidebarBehaviours)[number];
|
export type SidebarBehaviour = (typeof sidebarBehaviours)[number];
|
||||||
|
|
||||||
export const oldmarrImportConfigurationSchema = z.object({
|
export const oldmarrImportConfigurationSchema = z.object({
|
||||||
name: validation.board.name,
|
name: boardNameSchema,
|
||||||
onlyImportApps: z.boolean().default(false),
|
onlyImportApps: z.boolean().default(false),
|
||||||
sidebarBehaviour: z.enum(sidebarBehaviours).default(defaultSidebarBehaviour),
|
sidebarBehaviour: z.enum(sidebarBehaviours).default(defaultSidebarBehaviour),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useState } from "react";
|
|||||||
import { Popover, Progress } from "@mantine/core";
|
import { Popover, Progress } from "@mantine/core";
|
||||||
|
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { passwordRequirements } from "@homarr/validation";
|
import { passwordRequirements } from "@homarr/validation/user";
|
||||||
|
|
||||||
import { PasswordRequirement } from "./password-requirement";
|
import { PasswordRequirement } from "./password-requirement";
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from "./src";
|
|
||||||
@@ -5,8 +5,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
"./*": "./src/*.ts"
|
||||||
"./form": "./src/form/i18n.ts"
|
|
||||||
},
|
},
|
||||||
"typesVersions": {
|
"typesVersions": {
|
||||||
"*": {
|
"*": {
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const manageAppSchema = z.object({
|
export const appHrefSchema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.url()
|
||||||
|
.regex(/^https?:\/\//) // Only allow http and https for security reasons (javascript: is not allowed)
|
||||||
|
.or(z.literal(""))
|
||||||
|
.transform((value) => (value.length === 0 ? null : value))
|
||||||
|
.nullable();
|
||||||
|
|
||||||
|
export const appManageSchema = z.object({
|
||||||
name: z.string().trim().min(1).max(64),
|
name: z.string().trim().min(1).max(64),
|
||||||
description: z
|
description: z
|
||||||
.string()
|
.string()
|
||||||
@@ -9,14 +18,7 @@ const manageAppSchema = z.object({
|
|||||||
.transform((value) => (value.length === 0 ? null : value))
|
.transform((value) => (value.length === 0 ? null : value))
|
||||||
.nullable(),
|
.nullable(),
|
||||||
iconUrl: z.string().trim().min(1),
|
iconUrl: z.string().trim().min(1),
|
||||||
href: z
|
href: appHrefSchema,
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.url()
|
|
||||||
.regex(/^https?:\/\//) // Only allow http and https for security reasons (javascript: is not allowed)
|
|
||||||
.or(z.literal(""))
|
|
||||||
.transform((value) => (value.length === 0 ? null : value))
|
|
||||||
.nullable(),
|
|
||||||
pingUrl: z
|
pingUrl: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -27,12 +29,8 @@ const manageAppSchema = z.object({
|
|||||||
.nullable(),
|
.nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const editAppSchema = manageAppSchema.and(z.object({ id: z.string() }));
|
export const appCreateManySchema = z
|
||||||
|
.array(appManageSchema.omit({ iconUrl: true }).and(z.object({ iconUrl: z.string().min(1).nullable() })))
|
||||||
|
.min(1);
|
||||||
|
|
||||||
export const appSchemas = {
|
export const appEditSchema = appManageSchema.and(z.object({ id: z.string() }));
|
||||||
manage: manageAppSchema,
|
|
||||||
createMany: z
|
|
||||||
.array(manageAppSchema.omit({ iconUrl: true }).and(z.object({ iconUrl: z.string().min(1).nullable() })))
|
|
||||||
.min(1),
|
|
||||||
edit: editAppSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -18,27 +18,28 @@ const hexColorNullableSchema = hexColorSchema
|
|||||||
.nullable()
|
.nullable()
|
||||||
.transform((value) => (value?.trim().length === 0 ? null : value));
|
.transform((value) => (value?.trim().length === 0 ? null : value));
|
||||||
|
|
||||||
const boardNameSchema = z
|
export const boardNameSchema = z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
.max(255)
|
.max(255)
|
||||||
.regex(/^[A-Za-z0-9-\\_]*$/);
|
.regex(/^[A-Za-z0-9-\\_]*$/);
|
||||||
|
export const boardColumnCountSchema = z.number().min(1).max(24);
|
||||||
|
|
||||||
const byNameSchema = z.object({
|
export const boardByNameSchema = z.object({
|
||||||
name: boardNameSchema,
|
name: boardNameSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const renameSchema = z.object({
|
export const boardRenameSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: boardNameSchema,
|
name: boardNameSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const duplicateSchema = z.object({
|
export const boardDuplicateSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: boardNameSchema,
|
name: boardNameSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeVisibilitySchema = z.object({
|
export const boardChangeVisibilitySchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
visibility: z.enum(["public", "private"]),
|
visibility: z.enum(["public", "private"]),
|
||||||
});
|
});
|
||||||
@@ -48,7 +49,7 @@ const trimmedNullableString = z
|
|||||||
.nullable()
|
.nullable()
|
||||||
.transform((value) => (value?.trim().length === 0 ? null : value));
|
.transform((value) => (value?.trim().length === 0 ? null : value));
|
||||||
|
|
||||||
const savePartialSettingsSchema = z
|
export const boardSavePartialSettingsSchema = z
|
||||||
.object({
|
.object({
|
||||||
pageTitle: trimmedNullableString,
|
pageTitle: trimmedNullableString,
|
||||||
metaTitle: trimmedNullableString,
|
metaTitle: trimmedNullableString,
|
||||||
@@ -68,52 +69,28 @@ const savePartialSettingsSchema = z
|
|||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
const saveLayoutsSchema = z.object({
|
export const boardSaveLayoutsSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
layouts: z.array(
|
layouts: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string().trim().nonempty().max(32),
|
name: z.string().trim().nonempty().max(32),
|
||||||
columnCount: z.number().min(1).max(24),
|
columnCount: boardColumnCountSchema,
|
||||||
breakpoint: z.number().min(0).max(32767),
|
breakpoint: z.number().min(0).max(32767),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveSchema = z.object({
|
export const boardSaveSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
sections: z.array(sectionSchema),
|
sections: z.array(sectionSchema),
|
||||||
items: z.array(commonItemSchema),
|
items: z.array(commonItemSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createSchema = z.object({ name: boardNameSchema, columnCount: z.number().min(1).max(24), isPublic: z.boolean() });
|
export const boardCreateSchema = z.object({
|
||||||
|
|
||||||
const permissionsSchema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const savePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(boardPermissions));
|
|
||||||
|
|
||||||
z.object({
|
|
||||||
entityId: z.string(),
|
|
||||||
permissions: z.array(
|
|
||||||
z.object({
|
|
||||||
principalId: z.string(),
|
|
||||||
permission: zodEnumFromArray(boardPermissions),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const boardSchemas = {
|
|
||||||
name: boardNameSchema,
|
name: boardNameSchema,
|
||||||
byName: byNameSchema,
|
columnCount: boardColumnCountSchema,
|
||||||
savePartialSettings: savePartialSettingsSchema,
|
isPublic: z.boolean(),
|
||||||
saveLayouts: saveLayoutsSchema,
|
});
|
||||||
save: saveSchema,
|
|
||||||
create: createSchema,
|
export const boardSavePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(boardPermissions));
|
||||||
duplicate: duplicateSchema,
|
|
||||||
rename: renameSchema,
|
|
||||||
changeVisibility: changeVisibilitySchema,
|
|
||||||
permissions: permissionsSchema,
|
|
||||||
savePermissions: savePermissionsSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { createCustomErrorParams } from "./form/i18n";
|
import { createCustomErrorParams } from "./form/i18n";
|
||||||
|
|
||||||
const validFileNameSchema = z.string().regex(/^[\w\-. ]+$/);
|
export const certificateValidFileNameSchema = z.string().regex(/^[\w\-. ]+$/);
|
||||||
|
|
||||||
export const superRefineCertificateFile = (value: File | null, context: z.RefinementCtx) => {
|
export const superRefineCertificateFile = (value: File | null, context: z.RefinementCtx) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -13,7 +13,7 @@ export const superRefineCertificateFile = (value: File | null, context: z.Refine
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = validFileNameSchema.safeParse(value.name);
|
const result = certificateValidFileNameSchema.safeParse(value.name);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return context.addIssue({
|
return context.addIssue({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
@@ -46,7 +46,3 @@ export const superRefineCertificateFile = (value: File | null, context: z.Refine
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const certificateSchemas = {
|
|
||||||
validFileNameSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const paginatedSchema = z.object({
|
export const paginatedSchema = z.object({
|
||||||
search: z.string().optional(),
|
search: z.string().optional(),
|
||||||
pageSize: z.number().int().positive().default(10),
|
pageSize: z.number().int().positive().default(10),
|
||||||
page: z.number().int().positive().default(1),
|
page: z.number().int().positive().default(1),
|
||||||
@@ -10,26 +10,7 @@ export const byIdSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchSchema = z.object({
|
export const searchSchema = z.object({
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
limit: z.number().int().positive().default(10),
|
limit: z.number().int().positive().default(10),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mediaRequestOptionsSchema = z.object({
|
|
||||||
mediaId: z.number(),
|
|
||||||
mediaType: z.enum(["tv", "movie"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
const requestMediaSchema = z.object({
|
|
||||||
mediaType: z.enum(["tv", "movie"]),
|
|
||||||
mediaId: z.number(),
|
|
||||||
seasons: z.array(z.number().min(0)).optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const commonSchemas = {
|
|
||||||
paginated: paginatedSchema,
|
|
||||||
byId: byIdSchema,
|
|
||||||
search: searchSchema,
|
|
||||||
mediaRequestOptions: mediaRequestOptionsSchema,
|
|
||||||
requestMedia: requestMediaSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { everyoneGroup, groupPermissionKeys } from "@homarr/definitions";
|
|||||||
import { byIdSchema } from "./common";
|
import { byIdSchema } from "./common";
|
||||||
import { zodEnumFromArray } from "./enums";
|
import { zodEnumFromArray } from "./enums";
|
||||||
|
|
||||||
const createSchema = z.object({
|
export const groupCreateSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -16,35 +16,25 @@ const createSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSchema = createSchema.merge(byIdSchema);
|
export const groupUpdateSchema = groupCreateSchema.merge(byIdSchema);
|
||||||
|
|
||||||
const settingsSchema = z.object({
|
export const groupSettingsSchema = z.object({
|
||||||
homeBoardId: z.string().nullable(),
|
homeBoardId: z.string().nullable(),
|
||||||
mobileHomeBoardId: z.string().nullable(),
|
mobileHomeBoardId: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const savePartialSettingsSchema = z.object({
|
export const groupSavePartialSettingsSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
settings: settingsSchema.partial(),
|
settings: groupSettingsSchema.partial(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const savePermissionsSchema = z.object({
|
export const groupSavePermissionsSchema = z.object({
|
||||||
groupId: z.string(),
|
groupId: z.string(),
|
||||||
permissions: z.array(zodEnumFromArray(groupPermissionKeys)),
|
permissions: z.array(zodEnumFromArray(groupPermissionKeys)),
|
||||||
});
|
});
|
||||||
|
|
||||||
const savePositionsSchema = z.object({
|
export const groupSavePositionsSchema = z.object({
|
||||||
positions: z.array(z.string()),
|
positions: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupUserSchema = z.object({ groupId: z.string(), userId: z.string() });
|
export const groupUserSchema = z.object({ groupId: z.string(), userId: z.string() });
|
||||||
|
|
||||||
export const groupSchemas = {
|
|
||||||
create: createSchema,
|
|
||||||
update: updateSchema,
|
|
||||||
savePermissions: savePermissionsSchema,
|
|
||||||
groupUser: groupUserSchema,
|
|
||||||
savePartialSettings: savePartialSettingsSchema,
|
|
||||||
settings: settingsSchema,
|
|
||||||
savePositions: savePositionsSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const findIconsSchema = z.object({
|
export const iconsFindSchema = z.object({
|
||||||
searchText: z.string().optional(),
|
searchText: z.string().optional(),
|
||||||
limitPerGroup: z.number().min(1).max(500).default(12),
|
limitPerGroup: z.number().min(1).max(500).default(12),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const iconsSchemas = {
|
|
||||||
findIcons: findIconsSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { appSchemas } from "./app";
|
|
||||||
import { boardSchemas } from "./board";
|
|
||||||
import { certificateSchemas } from "./certificates";
|
|
||||||
import { commonSchemas } from "./common";
|
|
||||||
import { groupSchemas } from "./group";
|
|
||||||
import { iconsSchemas } from "./icons";
|
|
||||||
import { integrationSchemas } from "./integration";
|
|
||||||
import { locationSchemas } from "./location";
|
|
||||||
import { mediaSchemas } from "./media";
|
|
||||||
import { searchEngineSchemas } from "./search-engine";
|
|
||||||
import { settingsSchemas } from "./settings";
|
|
||||||
import { userSchemas } from "./user";
|
|
||||||
import { widgetSchemas } from "./widgets";
|
|
||||||
|
|
||||||
export const validation = {
|
|
||||||
user: userSchemas,
|
|
||||||
group: groupSchemas,
|
|
||||||
integration: integrationSchemas,
|
|
||||||
board: boardSchemas,
|
|
||||||
app: appSchemas,
|
|
||||||
widget: widgetSchemas,
|
|
||||||
location: locationSchemas,
|
|
||||||
icons: iconsSchemas,
|
|
||||||
searchEngine: searchEngineSchemas,
|
|
||||||
media: mediaSchemas,
|
|
||||||
settings: settingsSchemas,
|
|
||||||
common: commonSchemas,
|
|
||||||
certificates: certificateSchemas,
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
sectionSchema,
|
|
||||||
itemAdvancedOptionsSchema,
|
|
||||||
sharedItemSchema,
|
|
||||||
dynamicSectionOptionsSchema,
|
|
||||||
type BoardItemAdvancedOptions,
|
|
||||||
type BoardItemIntegration,
|
|
||||||
} from "./shared";
|
|
||||||
export { superRefineCertificateFile } from "./certificates";
|
|
||||||
export { passwordRequirements, usernameSchema } from "./user";
|
|
||||||
export { supportedMediaUploadFormats } from "./media";
|
|
||||||
export { zodEnumFromArray, zodUnionFromArray } from "./enums";
|
|
||||||
@@ -5,7 +5,7 @@ import { integrationKinds, integrationPermissions, integrationSecretKinds } from
|
|||||||
import { zodEnumFromArray } from "./enums";
|
import { zodEnumFromArray } from "./enums";
|
||||||
import { createSavePermissionsSchema } from "./permissions";
|
import { createSavePermissionsSchema } from "./permissions";
|
||||||
|
|
||||||
const integrationCreateSchema = z.object({
|
export const integrationCreateSchema = z.object({
|
||||||
name: z.string().nonempty().max(127),
|
name: z.string().nonempty().max(127),
|
||||||
url: z
|
url: z
|
||||||
.string()
|
.string()
|
||||||
@@ -21,7 +21,7 @@ const integrationCreateSchema = z.object({
|
|||||||
attemptSearchEngineCreation: z.boolean(),
|
attemptSearchEngineCreation: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrationUpdateSchema = z.object({
|
export const integrationUpdateSchema = z.object({
|
||||||
id: z.string().cuid2(),
|
id: z.string().cuid2(),
|
||||||
name: z.string().nonempty().max(127),
|
name: z.string().nonempty().max(127),
|
||||||
url: z.string().url(),
|
url: z.string().url(),
|
||||||
@@ -33,29 +33,4 @@ const integrationUpdateSchema = z.object({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const idSchema = z.object({
|
export const integrationSavePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(integrationPermissions));
|
||||||
id: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const testConnectionSchema = z.object({
|
|
||||||
id: z.string().cuid2().nullable(), // Is used to use existing secrets if they have not been updated
|
|
||||||
url: z.string().url(),
|
|
||||||
kind: zodEnumFromArray(integrationKinds),
|
|
||||||
secrets: z.array(
|
|
||||||
z.object({
|
|
||||||
kind: zodEnumFromArray(integrationSecretKinds),
|
|
||||||
value: z.string().nullable(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const savePermissionsSchema = createSavePermissionsSchema(zodEnumFromArray(integrationPermissions));
|
|
||||||
|
|
||||||
export const integrationSchemas = {
|
|
||||||
create: integrationCreateSchema,
|
|
||||||
update: integrationUpdateSchema,
|
|
||||||
delete: idSchema,
|
|
||||||
byId: idSchema,
|
|
||||||
testConnection: testConnectionSchema,
|
|
||||||
savePermissions: savePermissionsSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const citySchema = z.object({
|
|
||||||
id: z.number(),
|
|
||||||
name: z.string(),
|
|
||||||
country: z.string().optional(),
|
|
||||||
country_code: z.string().optional(),
|
|
||||||
latitude: z.number(),
|
|
||||||
longitude: z.number(),
|
|
||||||
population: z.number().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const searchCityInput = z.object({
|
|
||||||
query: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const searchCityOutput = z
|
|
||||||
.object({
|
|
||||||
results: z.array(citySchema),
|
|
||||||
})
|
|
||||||
.or(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
generationtime_ms: z.number(),
|
|
||||||
})
|
|
||||||
.refine((data) => Object.keys(data).length === 1, { message: "Invalid response" })
|
|
||||||
.transform(() => ({ results: [] })), // We fallback to empty array if no results
|
|
||||||
);
|
|
||||||
|
|
||||||
export const locationSchemas = {
|
|
||||||
searchCity: {
|
|
||||||
input: searchCityInput,
|
|
||||||
output: searchCityOutput,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -5,7 +5,7 @@ import { createCustomErrorParams } from "./form/i18n";
|
|||||||
|
|
||||||
export const supportedMediaUploadFormats = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"];
|
export const supportedMediaUploadFormats = ["image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml"];
|
||||||
|
|
||||||
export const uploadMediaSchema = zfd.formData({
|
export const mediaUploadSchema = zfd.formData({
|
||||||
file: zfd.file().superRefine((value: File | null, context: z.RefinementCtx) => {
|
file: zfd.file().superRefine((value: File | null, context: z.RefinementCtx) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return context.addIssue({
|
return context.addIssue({
|
||||||
@@ -38,7 +38,3 @@ export const uploadMediaSchema = zfd.formData({
|
|||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mediaSchemas = {
|
|
||||||
uploadMedia: uploadMediaSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const fromIntegrationSearchEngine = z.object({
|
|||||||
integrationId: z.string().optional(),
|
integrationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const manageSearchEngineSchema = z.object({
|
const baseSearchEngineManageSchema = z.object({
|
||||||
name: z.string().min(1).max(64),
|
name: z.string().min(1).max(64),
|
||||||
short: z.string().min(1).max(8),
|
short: z.string().min(1).max(8),
|
||||||
iconUrl: z.string().min(1),
|
iconUrl: z.string().min(1),
|
||||||
@@ -21,21 +21,18 @@ const manageSearchEngineSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const createManageSearchEngineSchema = <T extends ZodTypeAny>(
|
const createManageSearchEngineSchema = <T extends ZodTypeAny>(
|
||||||
callback: (schema: typeof manageSearchEngineSchema) => T,
|
callback: (schema: typeof baseSearchEngineManageSchema) => T,
|
||||||
) =>
|
) =>
|
||||||
z
|
z
|
||||||
.discriminatedUnion("type", [genericSearchEngine, fromIntegrationSearchEngine])
|
.discriminatedUnion("type", [genericSearchEngine, fromIntegrationSearchEngine])
|
||||||
.and(callback(manageSearchEngineSchema));
|
.and(callback(baseSearchEngineManageSchema));
|
||||||
|
|
||||||
const editSearchEngineSchema = createManageSearchEngineSchema((schema) =>
|
export const searchEngineManageSchema = createManageSearchEngineSchema((schema) => schema);
|
||||||
|
|
||||||
|
export const searchEngineEditSchema = createManageSearchEngineSchema((schema) =>
|
||||||
schema
|
schema
|
||||||
.extend({
|
.extend({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
})
|
})
|
||||||
.omit({ short: true }),
|
.omit({ short: true }),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const searchEngineSchemas = {
|
|
||||||
manage: createManageSearchEngineSchema((schema) => schema),
|
|
||||||
edit: editSearchEngineSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const initSettingsSchema = z.object({
|
export const settingsInitSchema = z.object({
|
||||||
analytics: z.object({
|
analytics: z.object({
|
||||||
enableGeneral: z.boolean(),
|
enableGeneral: z.boolean(),
|
||||||
enableWidgetData: z.boolean(),
|
enableWidgetData: z.boolean(),
|
||||||
@@ -14,7 +14,3 @@ const initSettingsSchema = z.object({
|
|||||||
noSiteLinksSearchBox: z.boolean(),
|
noSiteLinksSearchBox: z.boolean(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const settingsSchemas = {
|
|
||||||
init: initSettingsSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const passwordRequirements = [
|
|||||||
value: keyof TranslationObject["user"]["field"]["password"]["requirement"];
|
value: keyof TranslationObject["user"]["field"]["password"]["requirement"];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
const passwordSchema = z
|
export const userPasswordSchema = z
|
||||||
.string()
|
.string()
|
||||||
.min(8)
|
.min(8)
|
||||||
.max(255)
|
.max(255)
|
||||||
@@ -51,39 +51,39 @@ const addConfirmPasswordRefinement = <TObj extends { password: string; confirmPa
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseCreateUserSchema = z.object({
|
export const userBaseCreateSchema = z.object({
|
||||||
username: usernameSchema,
|
username: usernameSchema,
|
||||||
password: passwordSchema,
|
password: userPasswordSchema,
|
||||||
confirmPassword: z.string(),
|
confirmPassword: z.string(),
|
||||||
email: z.string().email().or(z.string().length(0).optional()),
|
email: z.string().email().or(z.string().length(0).optional()),
|
||||||
groupIds: z.array(z.string()),
|
groupIds: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createUserSchema = addConfirmPasswordRefinement(baseCreateUserSchema);
|
export const userCreateSchema = addConfirmPasswordRefinement(userBaseCreateSchema);
|
||||||
|
|
||||||
const initUserSchema = addConfirmPasswordRefinement(baseCreateUserSchema.omit({ groupIds: true }));
|
export const userInitSchema = addConfirmPasswordRefinement(userBaseCreateSchema.omit({ groupIds: true }));
|
||||||
|
|
||||||
const signInSchema = z.object({
|
export const userSignInSchema = z.object({
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
const registrationSchema = addConfirmPasswordRefinement(
|
export const userRegistrationSchema = addConfirmPasswordRefinement(
|
||||||
z.object({
|
z.object({
|
||||||
username: usernameSchema,
|
username: usernameSchema,
|
||||||
password: passwordSchema,
|
password: userPasswordSchema,
|
||||||
confirmPassword: z.string(),
|
confirmPassword: z.string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const registrationSchemaApi = registrationSchema.and(
|
export const userRegistrationApiSchema = userRegistrationSchema.and(
|
||||||
z.object({
|
z.object({
|
||||||
inviteId: z.string(),
|
inviteId: z.string(),
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const editProfileSchema = z.object({
|
export const userEditProfileSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: usernameSchema,
|
name: usernameSchema,
|
||||||
email: z
|
email: z
|
||||||
@@ -97,51 +97,33 @@ const editProfileSchema = z.object({
|
|||||||
|
|
||||||
const baseChangePasswordSchema = z.object({
|
const baseChangePasswordSchema = z.object({
|
||||||
previousPassword: z.string().min(1),
|
previousPassword: z.string().min(1),
|
||||||
password: passwordSchema,
|
password: userPasswordSchema,
|
||||||
confirmPassword: z.string(),
|
confirmPassword: z.string(),
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const changePasswordSchema = addConfirmPasswordRefinement(baseChangePasswordSchema.omit({ userId: true }));
|
export const userChangePasswordSchema = addConfirmPasswordRefinement(baseChangePasswordSchema.omit({ userId: true }));
|
||||||
|
|
||||||
const changePasswordApiSchema = addConfirmPasswordRefinement(baseChangePasswordSchema);
|
export const userChangePasswordApiSchema = addConfirmPasswordRefinement(baseChangePasswordSchema);
|
||||||
|
|
||||||
const changeHomeBoardSchema = z.object({
|
export const userChangeHomeBoardsSchema = z.object({
|
||||||
homeBoardId: z.string().nullable(),
|
homeBoardId: z.string().nullable(),
|
||||||
mobileHomeBoardId: z.string().nullable(),
|
mobileHomeBoardId: z.string().nullable(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeSearchPreferencesSchema = z.object({
|
export const userChangeSearchPreferencesSchema = z.object({
|
||||||
defaultSearchEngineId: z.string().min(1).nullable(),
|
defaultSearchEngineId: z.string().min(1).nullable(),
|
||||||
openInNewTab: z.boolean(),
|
openInNewTab: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const changeColorSchemeSchema = z.object({
|
export const userChangeColorSchemeSchema = z.object({
|
||||||
colorScheme: zodEnumFromArray(colorSchemes),
|
colorScheme: zodEnumFromArray(colorSchemes),
|
||||||
});
|
});
|
||||||
|
|
||||||
const firstDayOfWeekSchema = z.object({
|
export const userFirstDayOfWeekSchema = z.object({
|
||||||
firstDayOfWeek: z.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success),
|
firstDayOfWeek: z.custom<DayOfWeek>((value) => z.number().min(0).max(6).safeParse(value).success),
|
||||||
});
|
});
|
||||||
|
|
||||||
const pingIconsEnabledSchema = z.object({
|
export const userPingIconsEnabledSchema = z.object({
|
||||||
pingIconsEnabled: z.boolean(),
|
pingIconsEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userSchemas = {
|
|
||||||
signIn: signInSchema,
|
|
||||||
registration: registrationSchema,
|
|
||||||
registrationApi: registrationSchemaApi,
|
|
||||||
init: initUserSchema,
|
|
||||||
create: createUserSchema,
|
|
||||||
baseCreate: baseCreateUserSchema,
|
|
||||||
password: passwordSchema,
|
|
||||||
editProfile: editProfileSchema,
|
|
||||||
changePassword: changePasswordSchema,
|
|
||||||
changeHomeBoards: changeHomeBoardSchema,
|
|
||||||
changeSearchPreferences: changeSearchPreferencesSchema,
|
|
||||||
changePasswordApi: changePasswordApiSchema,
|
|
||||||
changeColorScheme: changeColorSchemeSchema,
|
|
||||||
firstDayOfWeek: firstDayOfWeekSchema,
|
|
||||||
pingIconsEnabled: pingIconsEnabledSchema,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { weatherWidgetSchemas } from "./weather";
|
|
||||||
|
|
||||||
export const widgetSchemas = {
|
|
||||||
weather: weatherWidgetSchemas,
|
|
||||||
};
|
|
||||||
12
packages/validation/src/widgets/media-request.ts
Normal file
12
packages/validation/src/widgets/media-request.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const mediaRequestOptionsSchema = z.object({
|
||||||
|
mediaId: z.number(),
|
||||||
|
mediaType: z.enum(["tv", "movie"]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mediaRequestRequestSchema = z.object({
|
||||||
|
mediaType: z.enum(["tv", "movie"]),
|
||||||
|
mediaId: z.number(),
|
||||||
|
seasons: z.array(z.number().min(0)).optional(),
|
||||||
|
});
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const atLocationInput = z.object({
|
|
||||||
longitude: z.number(),
|
|
||||||
latitude: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const atLocationOutput = z.object({
|
|
||||||
current_weather: z.object({
|
|
||||||
weathercode: z.number(),
|
|
||||||
temperature: z.number(),
|
|
||||||
windspeed: z.number(),
|
|
||||||
}),
|
|
||||||
daily: z.object({
|
|
||||||
time: z.array(z.string()),
|
|
||||||
weathercode: z.array(z.number()),
|
|
||||||
temperature_2m_max: z.array(z.number()),
|
|
||||||
temperature_2m_min: z.array(z.number()),
|
|
||||||
sunrise: z.array(z.string()),
|
|
||||||
sunset: z.array(z.string()),
|
|
||||||
wind_speed_10m_max: z.array(z.number()),
|
|
||||||
wind_gusts_10m_max: z.array(z.number()),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const weatherWidgetSchemas = {
|
|
||||||
atLocationInput,
|
|
||||||
atLocationOutput,
|
|
||||||
};
|
|
||||||
@@ -6,7 +6,7 @@ import { useForm } from "@homarr/form";
|
|||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { TextMultiSelect } from "@homarr/ui";
|
import { TextMultiSelect } from "@homarr/ui";
|
||||||
import type { BoardItemAdvancedOptions } from "@homarr/validation";
|
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||||
|
|
||||||
interface InnerProps {
|
interface InnerProps {
|
||||||
advancedOptions: BoardItemAdvancedOptions;
|
advancedOptions: BoardItemAdvancedOptions;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { zodResolver } from "@homarr/form";
|
|||||||
import { createModal, useModalAction } from "@homarr/modals";
|
import { createModal, useModalAction } from "@homarr/modals";
|
||||||
import type { SettingsContextProps } from "@homarr/settings";
|
import type { SettingsContextProps } from "@homarr/settings";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { zodErrorMap } from "@homarr/validation/form";
|
import { zodErrorMap } from "@homarr/validation/form/i18n";
|
||||||
|
|
||||||
import { widgetImports } from "..";
|
import { widgetImports } from "..";
|
||||||
import { getInputForType } from "../_inputs";
|
import { getInputForType } from "../_inputs";
|
||||||
|
|||||||
Reference in New Issue
Block a user