From 38a90787d0a61ea889cd044dc22db2307a5504a8 Mon Sep 17 00:00:00 2001
From: curet <76007534+curet-dev@users.noreply.github.com>
Date: Wed, 2 Jul 2025 19:52:33 +0200
Subject: [PATCH] feat: predefined domains (#822)
* feat(domains): add domains to server settings
* fix(domains): fix linting errors
* fix(domains): remove unused imports
* fix(urls): fix typo
* feat(domains): remove expiration date from domains
* feat(domains): changed domains from JSONB to TEXT[]
* fix(domains): linter errors
---------
Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com>
---
.../20250613161158_add_domains/migration.sql | 2 +
prisma/schema.prisma | 2 +
src/components/pages/serverSettings/index.tsx | 3 +
.../pages/serverSettings/parts/Domains.tsx | 94 +++++++++++++++++++
.../SettingsGenerators/GeneratorButton.tsx | 28 +++++-
.../pages/upload/UploadOptionsButton.tsx | 33 +++++--
src/lib/config/read/db.ts | 1 +
src/server/routes/api/server/settings.ts | 5 +
src/server/routes/api/user/urls/index.ts | 2 +-
9 files changed, 154 insertions(+), 16 deletions(-)
create mode 100644 prisma/migrations/20250613161158_add_domains/migration.sql
create mode 100644 src/components/pages/serverSettings/parts/Domains.tsx
diff --git a/prisma/migrations/20250613161158_add_domains/migration.sql b/prisma/migrations/20250613161158_add_domains/migration.sql
new file mode 100644
index 00000000..ab3c76c5
--- /dev/null
+++ b/prisma/migrations/20250613161158_add_domains/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Zipline" ADD COLUMN "domains" TEXT[] DEFAULT ARRAY[]::TEXT[];
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index dd919952..e9e54d7d 100755
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -135,6 +135,8 @@ model Zipline {
pwaDescription String @default("Zipline")
pwaThemeColor String @default("#000000")
pwaBackgroundColor String @default("#000000")
+
+ domains String[] @default([])
}
model User {
diff --git a/src/components/pages/serverSettings/index.tsx b/src/components/pages/serverSettings/index.tsx
index 898a92e6..9dc8c61c 100644
--- a/src/components/pages/serverSettings/index.tsx
+++ b/src/components/pages/serverSettings/index.tsx
@@ -3,6 +3,7 @@ import { Alert, Anchor, Collapse, Group, SimpleGrid, Skeleton, Stack, Title } fr
import useSWR from 'swr';
import dynamic from 'next/dynamic';
import { useDisclosure } from '@mantine/hooks';
+import Domains from './parts/Domains';
function SettingsSkeleton() {
return ;
@@ -105,6 +106,8 @@ export default function DashboardSettings() {
+
+
>
)}
diff --git a/src/components/pages/serverSettings/parts/Domains.tsx b/src/components/pages/serverSettings/parts/Domains.tsx
new file mode 100644
index 00000000..e554dc1e
--- /dev/null
+++ b/src/components/pages/serverSettings/parts/Domains.tsx
@@ -0,0 +1,94 @@
+import { Response } from '@/lib/api/response';
+import { Button, Group, LoadingOverlay, Paper, SimpleGrid, TextInput, Title } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { IconPlus, IconTrash } from '@tabler/icons-react';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import { settingsOnSubmit } from '../settingsOnSubmit';
+
+export default function Domains({
+ swr: { data, isLoading },
+}: {
+ swr: { data: Response['/api/server/settings'] | undefined; isLoading: boolean };
+}) {
+ const router = useRouter();
+ const [domains, setDomains] = useState([]);
+ const form = useForm({
+ initialValues: {
+ newDomain: '',
+ },
+ });
+
+ const onSubmit = settingsOnSubmit(router, form);
+
+ useEffect(() => {
+ if (!data) return;
+ const domainsData = Array.isArray(data.settings.domains)
+ ? data.settings.domains.map((d) => String(d))
+ : [];
+ setDomains(domainsData);
+ }, [data]);
+
+ const addDomain = () => {
+ const { newDomain } = form.values;
+ if (!newDomain) return;
+
+ const updatedDomains = [...domains, newDomain.trim()];
+ setDomains(updatedDomains);
+ form.setValues({ newDomain: '' });
+ onSubmit({ domains: updatedDomains });
+ };
+
+ const removeDomain = (index: number) => {
+ const updatedDomains = domains.filter((_, i) => i !== index);
+ setDomains(updatedDomains);
+ onSubmit({ domains: updatedDomains });
+ };
+
+ return (
+
+
+
+ Domains
+
+
+
+ }>
+ Add Domain
+
+
+
+
+ {domains.map((domain, index) => (
+
+
+
+ {domain}
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/pages/settings/parts/SettingsGenerators/GeneratorButton.tsx b/src/components/pages/settings/parts/SettingsGenerators/GeneratorButton.tsx
index 46cec2d4..0a366a3f 100755
--- a/src/components/pages/settings/parts/SettingsGenerators/GeneratorButton.tsx
+++ b/src/components/pages/settings/parts/SettingsGenerators/GeneratorButton.tsx
@@ -11,7 +11,6 @@ import {
Stack,
Switch,
Text,
- TextInput,
} from '@mantine/core';
import { IconDownload, IconEyeFilled, IconGlobe, IconPercentage, IconWriting } from '@tabler/icons-react';
import Link from 'next/link';
@@ -105,10 +104,22 @@ export default function GeneratorButton({
);
const { data: tokenData, isLoading, error } = useSWR('/api/user/token');
+ const { data: settingsData } = useSWR('/api/server/settings');
const isUnixLike = name === 'Flameshot' || name === 'Shell Script';
const onlyFile = generatorType === 'file';
+ const domains = Array.isArray(settingsData?.settings.domains)
+ ? settingsData?.settings.domains.map((d) => String(d))
+ : [];
+ const domainOptions = [
+ { value: '', label: 'Default Domain' },
+ ...domains.map((domain) => ({
+ value: domain,
+ label: domain,
+ })),
+ ] as { value: string; label: string; disabled?: boolean }[];
+
return (
<>
setOpen(false)} title={`Generate ${name} Uploader`}>
@@ -187,14 +198,21 @@ export default function GeneratorButton({
onChange={(value) => setOption({ maxViews: value === '' ? null : Number(value) })}
/>
- }
value={options.overrides_returnDomain ?? ''}
- onChange={(event) =>
- setOption({ overrides_returnDomain: event.currentTarget.value.trim() || null })
- }
+ onChange={(value) => setOption({ overrides_returnDomain: value || null })}
+ comboboxProps={{
+ withinPortal: true,
+ portalProps: {
+ style: {
+ zIndex: 100000000,
+ },
+ },
+ }}
/>
diff --git a/src/components/pages/upload/UploadOptionsButton.tsx b/src/components/pages/upload/UploadOptionsButton.tsx
index 4ea81b3f..171f41a4 100755
--- a/src/components/pages/upload/UploadOptionsButton.tsx
+++ b/src/components/pages/upload/UploadOptionsButton.tsx
@@ -62,9 +62,20 @@ export default function UploadOptionsButton({ folder, numFiles }: { folder?: str
const { data: folders } = useSWR>(
'/api/user/folders?noincl=true',
);
+ const { data: settingsData } = useSWR('/api/server/settings');
+
const combobox = useCombobox();
const [folderSearch, setFolderSearch] = useState('');
+ const domains = Array.isArray(settingsData?.settings.domains) ? settingsData.settings.domains : [];
+ const domainOptions = [
+ { value: '', label: 'Default Domain' },
+ ...domains.map((domain) => ({
+ value: domain,
+ label: domain,
+ })),
+ ];
+
useEffect(() => {
if (folder) return;
@@ -264,9 +275,7 @@ export default function UploadOptionsButton({ folder, numFiles }: { folder?: str
-
- No Folder
-
+ No Folder
{folders
?.filter((f) => f.name.toLowerCase().includes(folderSearch.toLowerCase().trim()))
@@ -279,7 +288,8 @@ export default function UploadOptionsButton({ folder, numFiles }: { folder?: str
-
Override Domain{' '}
@@ -293,12 +303,15 @@ export default function UploadOptionsButton({ folder, numFiles }: { folder?: str
description='Override the domain with this value. This will change the domain returned in your uploads. Leave blank to use the default domain.'
leftSection={}
value={options.overrides_returnDomain ?? ''}
- onChange={(event) =>
- setOption(
- 'overrides_returnDomain',
- event.currentTarget.value.trim() === '' ? null : event.currentTarget.value.trim(),
- )
- }
+ onChange={(value) => setOption('overrides_returnDomain', value || null)}
+ comboboxProps={{
+ withinPortal: true,
+ portalProps: {
+ style: {
+ zIndex: 100000000,
+ },
+ },
+ }}
/>
value.split(',').map((s) => s.trim())),
+ ]),
})
.partial()
.refine(
diff --git a/src/server/routes/api/user/urls/index.ts b/src/server/routes/api/user/urls/index.ts
index c18ea619..cd7bbf47 100755
--- a/src/server/routes/api/user/urls/index.ts
+++ b/src/server/routes/api/user/urls/index.ts
@@ -59,7 +59,7 @@ export default fastifyPlugin(
});
if (req.user.quota && req.user.quota.maxUrls && countUrls + 1 > req.user.quota.maxUrls)
return res.forbidden(
- `shortenning this url would exceed your quota of ${req.user.quota.maxUrls} urls`,
+ `Shortening this URL would exceed your quota of ${req.user.quota.maxUrls} URLs.`,
);
let maxViews: number | undefined;