From 1c81a6a86fecdda3c0ed555a8f61acfb9982a0ec Mon Sep 17 00:00:00 2001 From: Sonny Date: Sun, 9 Nov 2025 03:10:51 +0100 Subject: [PATCH] feat: allow adding links via ip --- app/links/validators/base_link_validator.ts | 9 ++++ app/links/validators/create_link_validator.ts | 7 +-- app/links/validators/update_link_validator.ts | 7 +-- inertia/components/form/form_collection.tsx | 2 +- inertia/components/form/form_link.tsx | 4 +- inertia/lib/navigation.ts | 47 ++++++++++++++++--- inertia/pages/links/create.tsx | 4 +- inertia/pages/links/delete.tsx | 4 +- inertia/pages/links/edit.tsx | 4 +- 9 files changed, 63 insertions(+), 25 deletions(-) create mode 100644 app/links/validators/base_link_validator.ts diff --git a/app/links/validators/base_link_validator.ts b/app/links/validators/base_link_validator.ts new file mode 100644 index 0000000..167c8c7 --- /dev/null +++ b/app/links/validators/base_link_validator.ts @@ -0,0 +1,9 @@ +import vine from '@vinejs/vine'; + +export const baseLinkValidator = vine.object({ + name: vine.string().trim().minLength(1).maxLength(254), + description: vine.string().trim().maxLength(300).optional(), + url: vine.string().url({ require_tld: false }).trim(), + favorite: vine.boolean(), + collectionId: vine.number(), +}); diff --git a/app/links/validators/create_link_validator.ts b/app/links/validators/create_link_validator.ts index 6bef08e..7fd46ce 100644 --- a/app/links/validators/create_link_validator.ts +++ b/app/links/validators/create_link_validator.ts @@ -1,11 +1,8 @@ +import { baseLinkValidator } from '#links/validators/base_link_validator'; import vine from '@vinejs/vine'; export const createLinkValidator = vine.compile( vine.object({ - name: vine.string().trim().minLength(1).maxLength(254), - description: vine.string().trim().maxLength(300).optional(), - url: vine.string().trim(), - favorite: vine.boolean(), - collectionId: vine.number(), + ...baseLinkValidator.getProperties(), }) ); diff --git a/app/links/validators/update_link_validator.ts b/app/links/validators/update_link_validator.ts index 0558918..af99699 100644 --- a/app/links/validators/update_link_validator.ts +++ b/app/links/validators/update_link_validator.ts @@ -1,13 +1,10 @@ import { params } from '#core/validators/params_object'; +import { baseLinkValidator } from '#links/validators/base_link_validator'; import vine from '@vinejs/vine'; export const updateLinkValidator = vine.compile( vine.object({ - name: vine.string().trim().minLength(1).maxLength(254), - description: vine.string().trim().maxLength(300).optional(), - url: vine.string().trim(), - favorite: vine.boolean(), - collectionId: vine.number(), + ...baseLinkValidator.getProperties(), params, }) diff --git a/inertia/components/form/form_collection.tsx b/inertia/components/form/form_collection.tsx index 339e1ec..c3c5b5a 100644 --- a/inertia/components/form/form_collection.tsx +++ b/inertia/components/form/form_collection.tsx @@ -21,7 +21,7 @@ interface FormCollectionProps extends FormLayoutProps { handleSubmit: () => void; } -export default function MantineFormCollection({ +export function MantineFormCollection({ data, errors, disableInputs = false, diff --git a/inertia/components/form/form_link.tsx b/inertia/components/form/form_link.tsx index 2d5708f..7b7a588 100644 --- a/inertia/components/form/form_link.tsx +++ b/inertia/components/form/form_link.tsx @@ -24,7 +24,7 @@ interface FormLinkProps extends FormLayoutProps { handleSubmit: () => void; } -export default function MantineFormLink({ +export function FormLink({ data, errors, collections, @@ -83,7 +83,7 @@ export default function MantineFormLink({ value: id.toString(), }))} onChange={(value) => setData('collectionId', value)} - value={data.collectionId.toString()} + value={data.collectionId?.toString()} readOnly={disableInputs} mt="md" searchable diff --git a/inertia/lib/navigation.ts b/inertia/lib/navigation.ts index c2dd396..7df0aa6 100644 --- a/inertia/lib/navigation.ts +++ b/inertia/lib/navigation.ts @@ -16,15 +16,50 @@ export const appendResourceId = ( ) => `${url}${resourceId ? `/${resourceId}` : ''}`; export function isValidHttpUrl(urlParam: string) { - let url; + const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}(:\d+)?(\/.*)?(\?.*)?(#[^#]*)?$/; + const domainRegex = + /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(:\d+)?(\/.*)?(\?.*)?(#[^#]*)?$/; + const simpleDomainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$/; - try { - url = new URL(urlParam); - } catch (_) { - return false; + let urlToTest = urlParam.trim(); + + if (urlToTest.startsWith('http://') || urlToTest.startsWith('https://')) { + try { + const url = new URL(urlToTest); + return url.protocol === 'http:' || url.protocol === 'https:'; + } catch (_) { + return false; + } } - return url.protocol === 'http:' || url.protocol === 'https:'; + if (ipv4Regex.test(urlToTest)) { + try { + new URL(`http://${urlToTest}`); + return true; + } catch (_) { + return false; + } + } + + if (domainRegex.test(urlToTest)) { + try { + new URL(`http://${urlToTest}`); + return true; + } catch (_) { + return false; + } + } + + if (simpleDomainRegex.test(urlToTest)) { + try { + new URL(`http://${urlToTest}`); + return true; + } catch (_) { + return false; + } + } + + return false; } export const generateShareUrl = ( diff --git a/inertia/pages/links/create.tsx b/inertia/pages/links/create.tsx index 507009d..6dcb261 100644 --- a/inertia/pages/links/create.tsx +++ b/inertia/pages/links/create.tsx @@ -2,7 +2,7 @@ import { useForm } from '@inertiajs/react'; import { route } from '@izzyjs/route/client'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import MantineFormLink from '~/components/form/form_link'; +import { FormLink } from '~/components/form/form_link'; import useSearchParam from '~/hooks/use_search_param'; import { isValidHttpUrl } from '~/lib/navigation'; import { Collection } from '~/types/app'; @@ -38,7 +38,7 @@ export default function CreateLinkPage({ }; return ( -