diff --git a/app/api/admin/domain/route.ts b/app/api/admin/domain/route.ts
index 8806479..f6cb71e 100644
--- a/app/api/admin/domain/route.ts
+++ b/app/api/admin/domain/route.ts
@@ -64,6 +64,9 @@ export async function POST(req: NextRequest) {
max_short_links: data.max_short_links,
max_email_forwards: data.max_email_forwards,
max_dns_records: data.max_dns_records,
+ min_url_length: data.min_url_length,
+ min_email_length: data.min_email_length,
+ min_record_length: data.min_record_length,
active: true,
});
@@ -93,6 +96,9 @@ export async function PUT(req: NextRequest) {
cf_email,
cf_record_types,
resend_api_key,
+ min_url_length,
+ min_email_length,
+ min_record_length,
max_short_links,
max_email_forwards,
max_dns_records,
@@ -115,6 +121,9 @@ export async function PUT(req: NextRequest) {
cf_record_types,
cf_api_key_encrypted: false,
resend_api_key,
+ min_url_length,
+ min_email_length,
+ min_record_length,
max_short_links,
max_email_forwards,
max_dns_records,
diff --git a/app/api/email/route.ts b/app/api/email/route.ts
index b573bfa..d4901c5 100644
--- a/app/api/email/route.ts
+++ b/app/api/email/route.ts
@@ -62,12 +62,6 @@ export async function POST(req: NextRequest) {
}
const prefix = emailAddress.split("@")[0];
- if (!prefix || prefix.length < 5) {
- return NextResponse.json("Email address length must be at least 5", {
- status: 400,
- });
- }
-
if (reservedAddressSuffix.includes(prefix)) {
return NextResponse.json("Invalid email address", { status: 400 });
}
diff --git a/app/api/v1/email/route.ts b/app/api/v1/email/route.ts
index 7335a29..ecbefe6 100644
--- a/app/api/v1/email/route.ts
+++ b/app/api/v1/email/route.ts
@@ -50,12 +50,6 @@ export async function POST(req: NextRequest) {
}
const [prefix, suffix] = emailAddress.split("@");
- if (!prefix || prefix.length < 5) {
- return NextResponse.json("Email address length must be at least 5", {
- status: 400,
- });
- }
-
const zones = await getDomainsByFeature("enable_email", true);
if (
!zones.length ||
@@ -64,6 +58,17 @@ export async function POST(req: NextRequest) {
return NextResponse.json("Invalid email suffix address", { status: 400 });
}
+ const limit_len =
+ zones.find((zone) => zone.domain_name === suffix)?.min_email_length ?? 3;
+ if (!prefix || prefix.length < limit_len) {
+ return NextResponse.json(
+ `Email address length must be at least ${limit_len}`,
+ {
+ status: 400,
+ },
+ );
+ }
+
if (reservedAddressSuffix.includes(prefix)) {
return NextResponse.json("Invalid email address", { status: 400 });
}
diff --git a/app/api/v1/short/route.ts b/app/api/v1/short/route.ts
index c868e11..8af90e4 100644
--- a/app/api/v1/short/route.ts
+++ b/app/api/v1/short/route.ts
@@ -55,6 +55,14 @@ export async function POST(req: Request) {
});
}
+ const limit_len =
+ zones.find((zone) => zone.domain_name === prefix)?.min_url_length ?? 3;
+ if (!url || url.length < limit_len) {
+ return Response.json(`Slug length must be at least ${limit_len}`, {
+ status: 400,
+ });
+ }
+
const res = await createUserShortUrl({
userId: user.id,
userName: user.name || "Anonymous",
diff --git a/app/manifest.json b/app/manifest.json
index 5778d65..81138ea 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -3,7 +3,7 @@
"short_name": "WR.DO",
"description": "Shorten links with analytics, manage emails and control subdomains—all on one platform.",
"appid": "com.wr.do",
- "versionName": "1.0.3",
+ "versionName": "1.0.4",
"versionCode": "1",
"start_url": "/",
"orientation": "portrait",
diff --git a/components/email/EmailSidebar.tsx b/components/email/EmailSidebar.tsx
index b792ac5..bc0e7cf 100644
--- a/components/email/EmailSidebar.tsx
+++ b/components/email/EmailSidebar.tsx
@@ -107,7 +107,7 @@ export default function EmailSidebar({
);
const { data: emailDomains, isLoading: isLoadingDomains } = useSWR<
- { domain_name: string }[]
+ { domain_name: string; min_email_length: number }[]
>("/api/domain?feature=email", fetcher, {
revalidateOnFocus: false,
dedupingInterval: 10000,
@@ -127,8 +127,11 @@ export default function EmailSidebar({
const totalPages = data ? Math.ceil(data.total / pageSize) : 0;
const handleSubmitEmail = async (emailSuffix: string) => {
- if (!emailSuffix || emailSuffix.length < 5) {
- toast.error("Email address characters must be at least 5");
+ const limit_len =
+ emailDomains?.find((d) => d.domain_name === domainSuffix)
+ ?.min_email_length ?? 1;
+ if (!emailSuffix || emailSuffix.length < limit_len) {
+ toast.error(`Email address characters must be at least ${limit_len}`);
return;
}
if (/[^a-zA-Z0-9_\-\.]/.test(emailSuffix)) {
diff --git a/components/forms/domain-form.tsx b/components/forms/domain-form.tsx
index ded5b86..d8ab89e 100644
--- a/components/forms/domain-form.tsx
+++ b/components/forms/domain-form.tsx
@@ -80,6 +80,9 @@ export function DomainForm({
cf_record_types: initData?.cf_record_types || "CNAME,A,TXT",
cf_api_key_encrypted: initData?.cf_api_key_encrypted || false,
resend_api_key: initData?.resend_api_key || "",
+ min_url_length: initData?.min_url_length,
+ min_email_length: initData?.min_email_length,
+ min_record_length: initData?.min_record_length,
max_short_links: initData?.max_short_links || 0,
max_email_forwards: initData?.max_email_forwards || 0,
max_dns_records: initData?.max_dns_records || 0,
@@ -549,6 +552,64 @@ export function DomainForm({
+
+
+
+ {t("Limit Configs")} ({t("Optional")})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{/* Action buttons */}
{type === "edit" && (
diff --git a/components/forms/record-form.tsx b/components/forms/record-form.tsx
index 442e1a0..187199c 100644
--- a/components/forms/record-form.tsx
+++ b/components/forms/record-form.tsx
@@ -66,6 +66,7 @@ export function RecordForm({
initData?.type || "CNAME",
);
const [currentZoneName, setCurrentZoneName] = useState(initData?.zone_name);
+ const [limitLen, setLimitLen] = useState(3);
const [email, setEmail] = useState(initData?.user.email || user.email);
const [allowedRecordTypes, setAllowedRecordTypes] = useState
([]);
const isAdmin = action.indexOf("admin") > -1;
@@ -93,7 +94,11 @@ export function RecordForm({
// Fetch the record domains
const { data: recordDomains, isLoading } = useSWR<
- { domain_name: string; cf_record_types: string }[]
+ {
+ domain_name: string;
+ cf_record_types: string;
+ min_record_length: number;
+ }[]
>("/api/domain?feature=record", fetcher, {
revalidateOnFocus: false,
dedupingInterval: 10000,
@@ -131,6 +136,10 @@ export function RecordForm({
.find((d) => d.domain_name === validDefaultDomain)!
.cf_record_types.split(","),
);
+ setLimitLen(
+ recordDomains.find((d) => d.domain_name === currentZoneName)
+ ?.min_record_length || 3,
+ );
}
}, [currentZoneName, recordDomains, validDefaultDomain]);
@@ -413,6 +422,7 @@ export function RecordForm({
id="name"
className="flex-1 shadow-inner"
size={32}
+ minLength={limitLen}
{...register("name")}
/>
{["CNAME", "A", "AAAA"].includes(currentRecordType) && (
diff --git a/components/forms/url-form.tsx b/components/forms/url-form.tsx
index a3e0231..57fb71e 100644
--- a/components/forms/url-form.tsx
+++ b/components/forms/url-form.tsx
@@ -5,6 +5,7 @@ import {
SetStateAction,
useEffect,
useMemo,
+ useState,
useTransition,
} from "react";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -58,6 +59,8 @@ export function UrlForm({
}: RecordFormProps) {
const [isPending, startTransition] = useTransition();
const [isDeleting, startDeleteTransition] = useTransition();
+ const [currentPrefix, setCurrentPrefix] = useState(initData?.prefix || "");
+ const [limitLen, setLimitLen] = useState(3);
const t = useTranslations("List");
const {
@@ -79,14 +82,12 @@ export function UrlForm({
},
});
- const { data: shortDomains, isLoading } = useSWR<{ domain_name: string }[]>(
- "/api/domain?feature=short",
- fetcher,
- {
- revalidateOnFocus: false,
- dedupingInterval: 10000,
- },
- );
+ const { data: shortDomains, isLoading } = useSWR<
+ { domain_name: string; min_url_length: number }[]
+ >("/api/domain?feature=short", fetcher, {
+ revalidateOnFocus: false,
+ dedupingInterval: 10000,
+ });
const validDefaultDomain = useMemo(() => {
if (!shortDomains?.length) return undefined;
@@ -104,9 +105,17 @@ export function UrlForm({
useEffect(() => {
if (validDefaultDomain) {
setValue("prefix", validDefaultDomain);
+ setCurrentPrefix(validDefaultDomain);
}
}, [validDefaultDomain]);
+ useEffect(() => {
+ setLimitLen(
+ shortDomains?.find((d) => d.domain_name === currentPrefix)
+ ?.min_url_length || 3,
+ );
+ }, [currentPrefix]);
+
const onSubmit = handleSubmit((data) => {
if (type === "add") {
handleCreateUrl(data);
@@ -233,6 +242,7 @@ export function UrlForm({