upd
This commit is contained in:
@@ -2,6 +2,7 @@ import { Suspense } from "react";
|
||||
import { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { UserAuthForm } from "@/components/forms/user-auth-form";
|
||||
@@ -29,10 +30,13 @@ export default function LoginPage() {
|
||||
</Link>
|
||||
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<div className="flex flex-col space-y-2 text-center">
|
||||
<Icons.logo className="mx-auto size-6" />
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
Welcome back
|
||||
</h1>
|
||||
<Icons.logo className="mx-auto size-12" />
|
||||
<div className="text-2xl font-semibold tracking-tight">
|
||||
<span>Welcome to</span>{" "}
|
||||
<span style={{ fontFamily: "Bahamas Bold" }}>
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enter your email to sign in to your account
|
||||
</p>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function IndexPage() {
|
||||
return <></>;
|
||||
}
|
||||
@@ -18,7 +18,6 @@ export async function POST(req: Request) {
|
||||
}
|
||||
|
||||
const { record, recordId } = await req.json();
|
||||
console.log(record, recordId);
|
||||
|
||||
const data = await updateDNSRecord(
|
||||
CLOUDFLARE_ZONE_ID,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { createUserShortUrlMeta, getUrlBySuffix } from "@/lib/dto/short-urls";
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
console.log("[api/s]", req.ip, req.geo);
|
||||
|
||||
const url = new URL(req.url);
|
||||
const slug = url.searchParams.get("slug");
|
||||
if (!slug) return Response.json(null);
|
||||
|
||||
const res = await getUrlBySuffix(slug);
|
||||
if (res?.target && res?.active === 1) {
|
||||
await createUserShortUrlMeta({
|
||||
urlId: res.id,
|
||||
click: 1,
|
||||
ip: req.ip ?? "127.0.0.0",
|
||||
city: req.geo?.city ?? "",
|
||||
region: req.geo?.region ?? "",
|
||||
country: req.geo?.country ?? "",
|
||||
latitude: req.geo?.latitude ?? "",
|
||||
longitude: req.geo?.longitude ?? "",
|
||||
});
|
||||
return Response.json(res.target);
|
||||
}
|
||||
return Response.json(null);
|
||||
} catch (error) {
|
||||
return Response.json(null);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -22,7 +22,7 @@ export default {
|
||||
}),
|
||||
Resend({
|
||||
apiKey: env.RESEND_API_KEY,
|
||||
from: "wrdo <dns@wr.do>",
|
||||
from: "wrdo <support@wr.do>",
|
||||
}),
|
||||
],
|
||||
} satisfies NextAuthConfig;
|
||||
|
||||
@@ -68,7 +68,10 @@ export function RecordForm({
|
||||
ttl: initData?.ttl || 1,
|
||||
proxied: initData?.proxied || false,
|
||||
comment: initData?.comment || "",
|
||||
name: initData?.name || "",
|
||||
name:
|
||||
(initData?.name.endsWith(".wr.do")
|
||||
? initData?.name.slice(0, -6)
|
||||
: initData?.name) || "",
|
||||
content: initData?.content || "",
|
||||
},
|
||||
});
|
||||
@@ -164,8 +167,7 @@ export function RecordForm({
|
||||
setCurrentRecordType(value);
|
||||
}}
|
||||
name={"type"}
|
||||
// disabled
|
||||
defaultValue="CNAME"
|
||||
defaultValue={initData?.type || "CNAME"}
|
||||
>
|
||||
<SelectTrigger className="w-full shadow-inner">
|
||||
<SelectValue placeholder="Select a type" />
|
||||
@@ -187,12 +189,17 @@ export function RecordForm({
|
||||
<Label className="sr-only" htmlFor="name">
|
||||
Name (required)
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
className="flex-1 shadow-inner"
|
||||
size={32}
|
||||
{...register("name")}
|
||||
/>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="name"
|
||||
className="flex-1 shadow-inner"
|
||||
size={32}
|
||||
{...register("name")}
|
||||
/>
|
||||
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-sm text-slate-500">
|
||||
.wr.do
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between p-1">
|
||||
{errors?.name ? (
|
||||
@@ -201,7 +208,7 @@ export function RecordForm({
|
||||
</p>
|
||||
) : (
|
||||
<p className="pb-0.5 text-[13px] text-muted-foreground">
|
||||
Required. Use @ for root
|
||||
Required. Use @ for root.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -251,7 +258,7 @@ export function RecordForm({
|
||||
setValue("ttl", Number(value));
|
||||
}}
|
||||
name="ttl"
|
||||
defaultValue="1"
|
||||
defaultValue={`${initData?.ttl}` || "1"}
|
||||
>
|
||||
<SelectTrigger className="w-full shadow-inner">
|
||||
<SelectValue placeholder="Select a time" />
|
||||
|
||||
@@ -79,7 +79,7 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
<Link
|
||||
href="/"
|
||||
style={{ fontFamily: "Bahamas Bold" }}
|
||||
className="text-2xl"
|
||||
className="text-2xl font-bold"
|
||||
>
|
||||
{siteConfig.name}
|
||||
</Link>
|
||||
@@ -178,39 +178,38 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* <div className="mt-auto xl:p-4">
|
||||
{isSidebarExpanded ? <UpgradeCard /> : null}
|
||||
</div> */}
|
||||
<p className="mx-3 mb-3 mt-auto font-mono text-xs text-muted-foreground/70">
|
||||
© 2024{" "}
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
>
|
||||
oiov
|
||||
</Link>
|
||||
. <br /> Built with{" "}
|
||||
<Link
|
||||
href="https://nextjs.org?ref=wrdo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
>
|
||||
Nextjs
|
||||
</Link>{" "}
|
||||
&{" "}
|
||||
<Link
|
||||
href="https://www.cloudflare.com?ref=wrdo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
>
|
||||
Cloudflare
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
{isSidebarExpanded && (
|
||||
<p className="mx-3 mb-3 mt-auto font-mono text-xs text-muted-foreground/70">
|
||||
© 2024{" "}
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
>
|
||||
oiov
|
||||
</Link>
|
||||
. <br /> Built with{" "}
|
||||
<Link
|
||||
href="https://nextjs.org?ref=wrdo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
>
|
||||
Nextjs
|
||||
</Link>{" "}
|
||||
&{" "}
|
||||
<Link
|
||||
href="https://www.cloudflare.com?ref=wrdo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="font-medium text-primary underline underline-offset-2"
|
||||
>
|
||||
Cloudflare
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
</ScrollArea>
|
||||
@@ -249,7 +248,7 @@ export function MobileSheetSidebar({ links }: DashboardSidebarProps) {
|
||||
<Image src="/favicon.ico" alt="logo" width={20} height={20} />
|
||||
<span
|
||||
style={{ fontFamily: "Bahamas Bold" }}
|
||||
className="pt-0.5 text-xl"
|
||||
className="pt-0.5 text-xl font-bold"
|
||||
>
|
||||
{siteConfig.name}
|
||||
</span>
|
||||
|
||||
@@ -50,7 +50,10 @@ export function NavBar({ scroll = false }: NavBarProps) {
|
||||
<div className="flex items-center gap-6 md:gap-10">
|
||||
<Link href="/" className="flex items-center space-x-1.5">
|
||||
<Icons.logo />
|
||||
<h1 style={{ fontFamily: "Bahamas Bold" }} className="text-2xl">
|
||||
<h1
|
||||
style={{ fontFamily: "Bahamas Bold" }}
|
||||
className="text-2xl font-bold"
|
||||
>
|
||||
{siteConfig.name}
|
||||
</h1>
|
||||
</Link>
|
||||
|
||||
@@ -12,18 +12,22 @@ export function SiteFooter({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
return (
|
||||
<footer className={cn("border-t", className)}>
|
||||
<div className="container grid max-w-6xl grid-cols-2 gap-6 py-14 md:grid-cols-5">
|
||||
<div className="col-span-full ml-4 flex flex-col items-start sm:col-span-1 md:col-span-2">
|
||||
<div className="col-span-full flex flex-col items-start sm:col-span-1 md:col-span-2">
|
||||
{/* <NewsletterForm /> */}
|
||||
<div className="flex items-center gap-6 md:gap-10">
|
||||
<Link href="/" className="flex items-center space-x-1.5">
|
||||
<Icons.logo />
|
||||
<h1 style={{ fontFamily: "Bahamas Bold" }} className="text-2xl">
|
||||
<h1
|
||||
style={{ fontFamily: "Bahamas Bold" }}
|
||||
className="text-2xl font-bold"
|
||||
>
|
||||
{siteConfig.name}
|
||||
</h1>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-4 text-sm">
|
||||
Craft DNS Records, Make Short Links.
|
||||
Craft DNS Records, Make Short Links. <br />
|
||||
Open source.
|
||||
</div>
|
||||
</div>
|
||||
{footerLinks.map((section) => (
|
||||
|
||||
@@ -17,8 +17,10 @@ export default async function HeroLanding() {
|
||||
)}
|
||||
>
|
||||
<span className="mr-3">🎉</span>{" "}
|
||||
<span style={{ fontFamily: "Bahamas Bold" }}>WR.DO </span> Beta
|
||||
Launching Now!
|
||||
<span className="font-bold" style={{ fontFamily: "Bahamas Bold" }}>
|
||||
WR.DO
|
||||
</span>
|
||||
Beta Launching Now!
|
||||
</Link>
|
||||
|
||||
<h1 className="text-balance font-satoshi text-[40px] font-black leading-[1.15] tracking-tight sm:text-5xl md:text-6xl md:leading-[1.15]">
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Loader2,
|
||||
LucideIcon,
|
||||
LucideProps,
|
||||
Mail,
|
||||
MessagesSquare,
|
||||
Moon,
|
||||
MoreVertical,
|
||||
@@ -121,4 +122,5 @@ export const Icons = {
|
||||
warning: AlertTriangle,
|
||||
globeLock: GlobeLock,
|
||||
link: Link,
|
||||
mail: Mail,
|
||||
};
|
||||
|
||||
+51
-50
@@ -1,6 +1,6 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
@@ -8,27 +8,27 @@ import {
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form"
|
||||
} from "react-hook-form";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
const Form = FormProvider
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
@@ -36,21 +36,21 @@ const FormField = <
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState, formState } = useFormContext()
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState, formState } = useFormContext();
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -59,36 +59,36 @@ const useFormField = () => {
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField()
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
@@ -97,15 +97,16 @@ const FormLabel = React.forwardRef<
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormLabel.displayName = "FormLabel"
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
@@ -119,15 +120,15 @@ const FormControl = React.forwardRef<
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormControl.displayName = "FormControl"
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField()
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
@@ -136,19 +137,19 @@ const FormDescription = React.forwardRef<
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormDescription.displayName = "FormDescription"
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message) : children
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -160,9 +161,9 @@ const FormMessage = React.forwardRef<
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
FormMessage.displayName = "FormMessage"
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
@@ -173,4 +174,4 @@ export {
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
||||
};
|
||||
|
||||
+6
-4
@@ -2,6 +2,8 @@ import { UserRole } from "@prisma/client";
|
||||
|
||||
import { SidebarNavItem } from "types";
|
||||
|
||||
import { siteConfig } from "./site";
|
||||
|
||||
export const sidebarLinks: SidebarNavItem[] = [
|
||||
{
|
||||
title: "MENU",
|
||||
@@ -44,11 +46,11 @@ export const sidebarLinks: SidebarNavItem[] = [
|
||||
{ href: "/", icon: "home", title: "Homepage" },
|
||||
{ href: "/docs", icon: "bookOpen", title: "Documentation" },
|
||||
{
|
||||
href: "#",
|
||||
icon: "messages",
|
||||
href: "mailto:" + siteConfig.mailSupport,
|
||||
icon: "mail",
|
||||
title: "Support",
|
||||
authorizeOnly: UserRole.USER,
|
||||
disabled: true,
|
||||
// authorizeOnly: UserRole.USER,
|
||||
// disabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { MarketingConfig } from "types";
|
||||
export const marketingConfig: MarketingConfig = {
|
||||
mainNav: [
|
||||
{
|
||||
title: "Documentation",
|
||||
title: "Docs",
|
||||
href: "/docs",
|
||||
},
|
||||
],
|
||||
|
||||
+37
-3
@@ -117,10 +117,9 @@ export async function deleteUserShortUrl(userId: string, urlId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserUrlMetaInfo(userId: string, urlId: string) {
|
||||
export async function getUserUrlMetaInfo(urlId: string) {
|
||||
return await prisma.urlMeta.findMany({
|
||||
where: {
|
||||
userId,
|
||||
urlId,
|
||||
},
|
||||
});
|
||||
@@ -130,11 +129,46 @@ export async function getUrlBySuffix(suffix: string) {
|
||||
return await prisma.userUrl.findFirst({
|
||||
where: {
|
||||
url: suffix,
|
||||
active: 1,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
target: true,
|
||||
active: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function createUserShortUrlMeta(
|
||||
data: Omit<UrlMeta, "id" | "createdAt" | "updatedAt">,
|
||||
) {
|
||||
try {
|
||||
const meta = await prisma.urlMeta.count({
|
||||
where: {
|
||||
ip: data.ip,
|
||||
urlId: data.urlId,
|
||||
},
|
||||
});
|
||||
|
||||
if (meta > 0) {
|
||||
await prisma.urlMeta.update({
|
||||
where: {
|
||||
ip: data.ip,
|
||||
urlId: data.urlId,
|
||||
},
|
||||
data: {
|
||||
click: {
|
||||
increment: 1,
|
||||
},
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const res = await prisma.urlMeta.create({
|
||||
data,
|
||||
});
|
||||
return { status: "success", data: res };
|
||||
}
|
||||
} catch (error) {
|
||||
return { status: error };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const createRecordSchema = z.object({
|
||||
.default("CNAME"),
|
||||
name: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9-.]+$/, "Invalid characters")
|
||||
.regex(/^[a-zA-Z0-9-]+$/, "Invalid characters")
|
||||
.min(1)
|
||||
.max(32),
|
||||
content: z
|
||||
|
||||
+33
-5
@@ -1,11 +1,39 @@
|
||||
export { auth as middleware } from "auth"
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "auth";
|
||||
|
||||
import { siteConfig } from "./config/site";
|
||||
import { createUserShortUrlMeta, getUrlBySuffix } from "./lib/dto/short-urls";
|
||||
|
||||
// export { auth as middleware } from "auth";
|
||||
|
||||
// Or like this if you need to do something here.
|
||||
// export default auth((req) => {
|
||||
// console.log(req.auth) // { session: { user: { ... } } }
|
||||
// })
|
||||
export default auth(async (req) => {
|
||||
// console.log(req.auth); // { session: { user: { ... } } }
|
||||
|
||||
if (req.url.includes("/s/")) {
|
||||
const slugRegex = /[^/]+$/;
|
||||
const match = req.url.match(slugRegex);
|
||||
if (match) {
|
||||
const res = await fetch(`${siteConfig.url}/api/s?slug=${match[0]}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
return NextResponse.redirect(`${siteConfig.url}`);
|
||||
}
|
||||
|
||||
const target = await res.json();
|
||||
if (!target) {
|
||||
return NextResponse.redirect(`${siteConfig.url}`);
|
||||
}
|
||||
return NextResponse.redirect(target);
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
});
|
||||
|
||||
// Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,6 +27,11 @@ const nextConfig = {
|
||||
},
|
||||
redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/s",
|
||||
destination: "/",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/0",
|
||||
destination: "https://www.oiov.dev",
|
||||
|
||||
@@ -147,7 +147,6 @@ ALTER TABLE "user_urls" ADD CONSTRAINT "user_urls_userId_fkey" FOREIGN KEY ("use
|
||||
CREATE TABLE "url_metas"
|
||||
(
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"urlId" TEXT NOT NULL,
|
||||
"click" INTEGER NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -157,8 +156,16 @@ CREATE TABLE "url_metas"
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "url_metas_userId_urlId_idx" ON "url_metas" ("userId", "urlId");
|
||||
CREATE INDEX "url_metas_urlId_idx" ON "url_metas" ("urlId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "url_metas" ADD CONSTRAINT "url_metas_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "url_metas" ADD CONSTRAINT "url_metas_urlId_fkey" FOREIGN KEY ("urlId") REFERENCES "user_urls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "url_metas" ADD CONSTRAINT "url_metas_urlId_fkey" FOREIGN KEY ("urlId") REFERENCES "user_urls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "url_metas" ADD COLUMN "ip" TEXT NOT NULL;
|
||||
CREATE UNIQUE INDEX "url_metas_ip_key" ON "url_metas" ("ip");
|
||||
|
||||
ALTER TABLE "url_metas" ADD COLUMN "city" TEXT;
|
||||
ALTER TABLE "url_metas" ADD COLUMN "country" TEXT;
|
||||
ALTER TABLE "url_metas" ADD COLUMN "region" TEXT;
|
||||
ALTER TABLE "url_metas" ADD COLUMN "latitude" TEXT;
|
||||
ALTER TABLE "url_metas" ADD COLUMN "longitude" TEXT;
|
||||
+11
-7
@@ -65,7 +65,6 @@ model User {
|
||||
sessions Session[]
|
||||
UserRecord UserRecord[]
|
||||
UserUrl UserUrl[]
|
||||
UrlMeta UrlMeta[]
|
||||
|
||||
@@map(name: "users")
|
||||
}
|
||||
@@ -122,17 +121,22 @@ model UserUrl {
|
||||
}
|
||||
|
||||
model UrlMeta {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
urlId String
|
||||
click Int
|
||||
id String @id @default(cuid())
|
||||
urlId String
|
||||
click Int
|
||||
|
||||
ip String @unique
|
||||
city String?
|
||||
country String?
|
||||
region String?
|
||||
latitude String?
|
||||
longitude String?
|
||||
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userUrl UserUrl @relation(fields: [urlId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId, urlId])
|
||||
@@index([urlId])
|
||||
@@map(name: "url_metas")
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -3,12 +3,12 @@
|
||||
"short_name": "WR.DO",
|
||||
"icons": [
|
||||
{
|
||||
"src": "./logo.svg",
|
||||
"src": "./_static/logo.svg",
|
||||
"sizes": "192x192",
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"src": "./logo.svg",
|
||||
"src": "./_static/logo.svg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/svg+xml"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user