feat: support multi domain config

This commit is contained in:
oiov
2025-03-26 22:00:52 +08:00
parent 097083dc11
commit a2b9540cf2
17 changed files with 63 additions and 20 deletions
+1 -1
View File
@@ -1,3 +1,4 @@
import { siteConfig } from "@/config/site";
import HeroLanding, { LandingImages } from "@/components/sections/hero-landing";
import { PricingSection } from "@/components/sections/pricing";
@@ -7,7 +8,6 @@ export default function IndexPage() {
return (
<>
<HeroLanding />
{/* <PreviewLanding /> */}
<LandingImages />
<PricingSection />
</>
-1
View File
@@ -1,4 +1,3 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { siteConfig } from "@/config/site";
+5 -3
View File
@@ -353,7 +353,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
<TableCell className="col-span-1 flex items-center gap-1">
<Link
className="overflow-hidden overflow-ellipsis whitespace-normal text-slate-600 hover:text-blue-400 hover:underline dark:text-slate-400"
href={`/s/${short.url}`}
href={`https://${short.prefix}/s/${short.url}`}
target="_blank"
prefetch={false}
title={short.url}
@@ -361,7 +361,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
{short.url}
</Link>
<CopyButton
value={`${siteConfig.url}/s/${short.url}`}
value={`${short.prefix}/s/${short.url}`}
className={cn(
"size-[25px]",
"duration-250 transition-all group-hover:opacity-100",
@@ -434,7 +434,9 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
onClick={() => {
setSelectedUrlId(short.id!);
setShowQrcode(!isShowQrcode);
handleQrcode(`https://wr.do/s/${short.url}`);
handleQrcode(
`https://${short.prefix}/s/${short.url}`,
);
}}
>
<Icons.qrcode className="mx-0.5 size-4" />
+2 -1
View File
@@ -25,13 +25,14 @@ export async function POST(req: Request) {
const { data } = await req.json();
const { target, url, visible, active, expiration } =
const { target, url, prefix, visible, active, expiration } =
createUrlSchema.parse(data);
const res = await createUserShortUrl({
userId: user.id,
userName: user.name || "Anonymous",
target,
url,
prefix,
visible,
active,
expiration,
-1
View File
@@ -1,5 +1,4 @@
import { env } from "@/env.mjs";
import { getUserRecords } from "@/lib/dto/cloudflare-dns-record";
import { deleteUserShortUrl } from "@/lib/dto/short-urls";
import { checkUserStatus } from "@/lib/dto/user";
import { getCurrentUser } from "@/lib/session";
+2 -1
View File
@@ -24,7 +24,7 @@ export async function POST(req: Request) {
});
}
const { target, url, visible, active, id, expiration } =
const { target, url, prefix, visible, active, id, expiration } =
createUrlSchema.parse(data);
const res = await updateUserShortUrl({
id,
@@ -32,6 +32,7 @@ export async function POST(req: Request) {
userName: "",
target,
url,
prefix,
visible,
active,
expiration,
-1
View File
@@ -6,7 +6,6 @@ import {
} from "@/lib/dto/short-urls";
import { checkUserStatus } from "@/lib/dto/user";
import { getCurrentUser } from "@/lib/session";
import { createUrlSchema } from "@/lib/validations/url";
export async function POST(req: Request) {
try {
+2 -1
View File
@@ -18,13 +18,14 @@ export async function POST(req: Request) {
});
}
const { target, url, visible, active, id, expiration } =
const { target, url, prefix, visible, active, id, expiration } =
createUrlSchema.parse(data);
const res = await updateUserShortUrl({
id,
userId: user.id,
userName: user.name || "Anonymous",
target,
prefix,
url,
visible,
active,
+38 -9
View File
@@ -7,6 +7,7 @@ import { Sparkles } from "lucide-react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { siteConfig } from "@/config/site";
import { ShortUrlFormData } from "@/lib/dto/short-urls";
import { EXPIRATION_ENUMS } from "@/lib/enums";
import { generateUrlSuffix } from "@/lib/utils";
@@ -63,6 +64,7 @@ export function UrlForm({
target: initData?.target || "",
url: initData?.url || "",
active: initData?.active || 1,
prefix: initData?.prefix || siteConfig.shortDomains[0],
visible: initData?.visible || 0,
expiration: initData?.expiration || "-1",
},
@@ -148,7 +150,7 @@ export function UrlForm({
onSubmit={onSubmit}
>
<div className="items-center justify-start gap-4 md:flex">
<FormSectionColumns title="Target">
<FormSectionColumns title="Target URL">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="target">
Target
@@ -172,19 +174,38 @@ export function UrlForm({
)}
</div>
</FormSectionColumns>
<FormSectionColumns title="Url Suffix">
<FormSectionColumns title="Short Link">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="url">
Url
</Label>
<div className="relative flex items-center">
<span className="pointer-events-none absolute left-3 whitespace-nowrap text-sm text-gray-400">
https://wr.do/s/
</span>
<Select
onValueChange={(value: string) => {
setValue("prefix", value);
}}
name="prefix"
defaultValue={initData?.prefix || siteConfig.shortDomains[0]}
disabled={type === "edit"}
>
<SelectTrigger className="w-2/5 rounded-r-none shadow-inner">
<SelectValue placeholder="Select a domain" />
</SelectTrigger>
<SelectContent>
{siteConfig.shortDomains.map((v) => (
<SelectItem key={v} value={v}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
{/* <span className="pointer-events-none absolute left-20 whitespace-nowrap text-sm text-gray-400">
/s/
</span> */}
<Input
id="url"
className="flex-1 rounded-r-none pl-[116px] shadow-inner"
className="w-3/5 flex-1 rounded-none pl-[8px] shadow-inner"
size={20}
{...register("url")}
disabled={type === "edit"}
@@ -210,7 +231,7 @@ export function UrlForm({
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
A random url suffix.
A random url suffix. Final url likewr.do/s/suffix
</p>
)}
</div>
@@ -241,7 +262,15 @@ export function UrlForm({
Expiration time, default for never.
</p>
</FormSectionColumns>
<FormSectionColumns title="Visible">
{/* <div>
<p className="text-sm text-gray-700 dark:text-white">
Your Final URL:
</p>
<p className="text-sm text-gray-700 dark:text-white">
{getValues("prefix")}/s/{getValues("url")}
</p>
</div> */}
{/* <FormSectionColumns title="Visible">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="visible">
Visible
@@ -257,7 +286,7 @@ export function UrlForm({
<p className="p-1 text-[13px] text-muted-foreground">
Public or private short url.
</p>
</FormSectionColumns>
</FormSectionColumns> */}
{/* <FormSectionColumns title="Active">
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="active">
+2
View File
@@ -5,6 +5,7 @@ const site_url = env.NEXT_PUBLIC_APP_URL;
const free_recored_quota = env.NEXT_PUBLIC_FREE_RECORD_QUOTA;
const free_url_quota = env.NEXT_PUBLIC_FREE_URL_QUOTA;
const open_signup = env.NEXT_PUBLIC_OPEN_SIGNUP;
const short_domains = env.NEXT_PUBLIC_SHORT_DOMAINS || "";
export const siteConfig: SiteConfig = {
name: "WR.DO",
@@ -22,6 +23,7 @@ export const siteConfig: SiteConfig = {
url: Number(free_url_quota),
},
openSignup: open_signup === "1" ? true : false,
shortDomains: short_domains.split(","),
};
export const footerLinks: SidebarNavItem[] = [
+2
View File
@@ -26,6 +26,7 @@ export const env = createEnv({
NEXT_PUBLIC_FREE_RECORD_QUOTA: z.string().min(1).default("3"),
NEXT_PUBLIC_FREE_URL_QUOTA: z.string().min(1).default("100"),
NEXT_PUBLIC_OPEN_SIGNUP: z.string().min(1).default("1"),
NEXT_PUBLIC_SHORT_DOMAINS: z.string().min(1).default(""),
},
runtimeEnv: {
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
@@ -40,6 +41,7 @@ export const env = createEnv({
NEXT_PUBLIC_FREE_RECORD_QUOTA: process.env.NEXT_PUBLIC_FREE_RECORD_QUOTA,
NEXT_PUBLIC_FREE_URL_QUOTA: process.env.NEXT_PUBLIC_FREE_URL_QUOTA,
NEXT_PUBLIC_OPEN_SIGNUP: process.env.NEXT_PUBLIC_OPEN_SIGNUP,
NEXT_PUBLIC_SHORT_DOMAINS: process.env.NEXT_PUBLIC_SHORT_DOMAINS,
CLOUDFLARE_ZONE_ID: process.env.CLOUDFLARE_ZONE_ID,
CLOUDFLARE_API_KEY: process.env.CLOUDFLARE_API_KEY,
CLOUDFLARE_EMAIL: process.env.CLOUDFLARE_EMAIL,
+4
View File
@@ -9,6 +9,7 @@ export interface ShortUrlFormData {
userName: string;
target: string;
url: string;
prefix: string;
visible: number;
active: number;
expiration: string;
@@ -103,6 +104,7 @@ export async function createUserShortUrl(data: ShortUrlFormData) {
userName: data.userName || "Anonymous",
target: data.target,
url: data.url,
prefix: data.prefix,
visible: data.visible,
active: data.active,
expiration: data.expiration,
@@ -127,6 +129,7 @@ export async function updateUserShortUrl(data: ShortUrlFormData) {
target: data.target,
url: data.url,
visible: data.visible,
prefix: data.prefix,
// active: data.active,
expiration: data.expiration,
updatedAt: new Date().toISOString(),
@@ -206,6 +209,7 @@ export async function getUrlBySuffix(suffix: string) {
id: true,
target: true,
active: true,
prefix: true,
expiration: true,
updatedAt: true,
},
+1
View File
@@ -23,4 +23,5 @@ export const createUrlSchema = z.object({
expiration: z.string().default("-1"),
visible: z.number().default(1),
active: z.number().default(1),
prefix: z.string().default("wr.do"),
});
@@ -126,6 +126,7 @@ CREATE TABLE "user_urls"
"userName" TEXT NOT NULL,
"target" TEXT NOT NULL,
"url" TEXT NOT NULL,
"prefix" TEXT NOT NULL,
"visible" INTEGER NOT NULL DEFAULT 0,
"active" INTEGER NOT NULL DEFAULT 1,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+1
View File
@@ -110,6 +110,7 @@ model UserUrl {
userName String
target String
url String @unique
prefix String
visible Int @default(0)
active Int @default(1)
expiration String @default("-1")
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -19,6 +19,7 @@ export type SiteConfig = {
url: number;
};
openSignup: boolean;
shortDomains: string[];
};
export type NavItem = {