diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index 5a61bc1..76fcd45 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - pwd tags: - "v*.*.*" pull_request: diff --git a/README-zh.md b/README-zh.md index 5735f0c..2e52eb2 100644 --- a/README-zh.md +++ b/README-zh.md @@ -73,7 +73,7 @@ WR.DO 是一个一站式网络工具平台,集成短链服务、临时邮箱 ### 使用 Vercel 部署 -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo&env=DATABASE_URL&env=AUTH_SECRET&env=RESEND_API_KEY&env=NEXT_PUBLIC_EMAIL_R2_DOMAIN&env=GITHUB_TOKEN) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo) 记得填写必要的环境变量。 @@ -107,7 +107,9 @@ pnpm db:push #### 管理员初始化 -Follow https://localhost:3000/setup +> 此初始化引导在 v1.0.2 版本后, 不再是必要步骤 + +访问 https://localhost:3000/setup ## 环境变量 diff --git a/README.md b/README.md index 5aa1f53..846c1bc 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ See step by step installation tutorial at [Quick Start for Developer](https://wr ### Deploy with Vercel -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo&env=DATABASE_URL&env=AUTH_SECRET&env=RESEND_API_KEY&env=NEXT_PUBLIC_EMAIL_R2_DOMAIN&env=GITHUB_TOKEN) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo) Remember to fill in the necessary environment variables. @@ -117,6 +117,8 @@ pnpm db:push #### Setup Admin Panel +> After v1.0.2, this setup guide is not needed anymore + Follow https://localhost:3000/setup ## Environment Variables diff --git a/actions/update-user-password.ts b/actions/update-user-password.ts new file mode 100644 index 0000000..cbbe436 --- /dev/null +++ b/actions/update-user-password.ts @@ -0,0 +1,39 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { auth } from "@/auth"; +import { hash } from "bcrypt"; + +import { prisma } from "@/lib/db"; +import { userPasswordSchema } from "@/lib/validations/user"; + +export type FormData = { + password: string; +}; + +export async function updateUserPassword(userId: string, data: FormData) { + try { + const session = await auth(); + + if (!session?.user || session?.user.id !== userId) { + throw new Error("Unauthorized"); + } + + const { password } = userPasswordSchema.parse(data); + + await prisma.user.update({ + where: { + id: userId, + }, + data: { + password: await hash(password, 10), + }, + }); + + revalidatePath("/dashboard/settings"); + return { status: "success" }; + } catch (error) { + // console.log(error) + return { status: "error" }; + } +} diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 154292c..b2cca51 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -51,7 +51,7 @@ export default function LoginPage() { 200 new account sign-ups each day.

*/} -

+

{t("By clicking continue, you agree to our")}{" "} >( "/api/admin/configs", @@ -33,6 +35,16 @@ export default function AppConfigs({}: {}) { if (!isLoading && configs?.system_notification) { setNotification(configs.system_notification); } + // 计算登录方式数量 + if (!isLoading) { + let count = 0; + if (configs?.enable_google_oauth) count++; + if (configs?.enable_github_oauth) count++; + if (configs?.enable_liunxdo_oauth) count++; + if (configs?.enable_resend_email_login) count++; + if (configs?.enable_email_password_login) count++; + setLoginMethodCount(count); + } }, [configs, isLoading]); const handleChange = (value: any, key: string, type: string) => { @@ -83,21 +95,27 @@ export default function AppConfigs({}: {}) { /> )} +

{t("Login Methods")}

+

{t("Select the login methods that users can use to log in")}

- + + + {loginMethodCount} {configs && ( <>
-

GitHub OAuth

+

+ GitHub OAuth +

@@ -106,7 +124,10 @@ export default function AppConfigs({}: {}) { />
-

Google OAuth

+

+ + Google OAuth +

@@ -115,7 +136,14 @@ export default function AppConfigs({}: {}) { />
-

LinuxDo OAuth

+

+ linuxdo + LinuxDo OAuth +

@@ -124,7 +152,10 @@ export default function AppConfigs({}: {}) { />
-

{t("Resend Email")}

+

+ + {t("Resend Email")} +

@@ -132,10 +163,27 @@ export default function AppConfigs({}: {}) { } />
+
+

+ + {t("Email Password")} +

+ + handleChange( + v, + "enable_email_password_login", + "BOOLEAN", + ) + } + /> +
)}
+

{t("Subdomain Apply Mode")}

@@ -154,7 +202,6 @@ export default function AppConfigs({}: {}) { /> )}
-

{t("Notification")}

diff --git a/app/(protected)/dashboard/records/record-list.tsx b/app/(protected)/dashboard/records/record-list.tsx index a678b7b..ced72b4 100644 --- a/app/(protected)/dashboard/records/record-list.tsx +++ b/app/(protected)/dashboard/records/record-list.tsx @@ -154,15 +154,15 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
{t("Subdomain List")} - {t("Please read the")}{" "} + {t("Before using please read the")}{" "} {t("legitimacy review")} - {" "} - {t("before using")}. {t("See")}{" "} + + . {t("See")}{" "}
+ diff --git a/app/(protected)/dashboard/settings/page.tsx b/app/(protected)/dashboard/settings/page.tsx index 6f46dd7..6276cb7 100644 --- a/app/(protected)/dashboard/settings/page.tsx +++ b/app/(protected)/dashboard/settings/page.tsx @@ -6,6 +6,7 @@ import { DeleteAccountSection } from "@/components/dashboard/delete-account"; import { DashboardHeader } from "@/components/dashboard/header"; import { UserApiKeyForm } from "@/components/forms/user-api-key-form"; import { UserNameForm } from "@/components/forms/user-name-form"; +import { UserPasswordForm } from "@/components/forms/user-password-form"; import { UserRoleForm } from "@/components/forms/user-role-form"; export const metadata = constructMetadata({ @@ -29,6 +30,7 @@ export default async function SettingsPage() { {user.role === "ADMIN" && ( )} + )} - + {action.indexOf("admin") === -1 && ( + + )}
diff --git a/app/(protected)/setup/guide.tsx b/app/(protected)/setup/guide.tsx index 85a263c..e981bb4 100644 --- a/app/(protected)/setup/guide.tsx +++ b/app/(protected)/setup/guide.tsx @@ -225,6 +225,9 @@ function SetAdminRole({ id, email }: { id: string; email: string }) {
+

+ • {t("After v1-0-2, this setup guide is not needed anymore")}. +

•{" "} {t( diff --git a/app/api/admin/configs/route.ts b/app/api/admin/configs/route.ts index 911407f..b9b2aa5 100644 --- a/app/api/admin/configs/route.ts +++ b/app/api/admin/configs/route.ts @@ -23,6 +23,7 @@ export async function GET(req: NextRequest) { "enable_google_oauth", "enable_liunxdo_oauth", "enable_resend_email_login", + "enable_email_password_login", ]); return Response.json(configs, { status: 200 }); diff --git a/app/api/auth/credentials/route.ts b/app/api/auth/credentials/route.ts new file mode 100644 index 0000000..8e4b777 --- /dev/null +++ b/app/api/auth/credentials/route.ts @@ -0,0 +1,46 @@ +import { NextRequest } from "next/server"; +import { compare, hash } from "bcrypt"; + +import { prisma } from "@/lib/db"; + +export async function POST(req: NextRequest) { + try { + const { email, password, name } = await req.json(); + if (!email || !password) { + return Response.json("email and password is required", { status: 400 }); + } + + const user = await prisma.user.findUnique({ + where: { + email, + }, + }); + + if (!user) { + const newUser = await prisma.user.create({ + data: { + name: "", + email, + password: await hash(password, 10), + active: 1, + role: "USER", + team: "free", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }, + }); + return Response.json(newUser, { status: 200 }); + } else { + const passwordCorrect = await compare(password, user.password || ""); + + if (passwordCorrect) { + return Response.json(user, { status: 200 }); + } + } + + return Response.json(null, { status: 400 }); + } catch (error) { + console.error("[Auth Error]", error); + return Response.json(error.message || "Server error", { status: 500 }); + } +} diff --git a/app/api/feature/route.ts b/app/api/feature/route.ts index 0eb336b..c708fb2 100644 --- a/app/api/feature/route.ts +++ b/app/api/feature/route.ts @@ -2,7 +2,7 @@ import { getMultipleConfigs } from "@/lib/dto/system-config"; export const dynamic = "force-dynamic"; -export async function GET(req: Request) { +export async function GET() { try { const configs = await getMultipleConfigs([ "enable_user_registration", @@ -12,12 +12,14 @@ export async function GET(req: Request) { "enable_google_oauth", "enable_liunxdo_oauth", "enable_resend_email_login", + "enable_email_password_login", ]); return Response.json({ google: configs.enable_google_oauth, github: configs.enable_github_oauth, linuxdo: configs.enable_liunxdo_oauth, resend: configs.enable_resend_email_login, + credentials: configs.enable_email_password_login, registration: configs.enable_user_registration, }); } catch (error) { diff --git a/app/api/record/add/route.ts b/app/api/record/add/route.ts index 4158220..115a016 100644 --- a/app/api/record/add/route.ts +++ b/app/api/record/add/route.ts @@ -58,10 +58,10 @@ export async function POST(req: Request) { if (!matchedZone) { return Response.json( - `No matching zone found for domain: ${record_name}`, + `No matching zone found for domain: ${record.zone_name}`, { status: 400, - statusText: "Invalid domain", + statusText: "Invalid zone name", }, ); } @@ -78,6 +78,7 @@ export async function POST(req: Request) { record.type, record_name, record.content, + record.zone_name, 1, ); if (user_record && user_record.length > 0) { diff --git a/app/api/record/admin/add/route.ts b/app/api/record/admin/add/route.ts index 26a7ff0..7f07c0a 100644 --- a/app/api/record/admin/add/route.ts +++ b/app/api/record/admin/add/route.ts @@ -87,6 +87,7 @@ export async function POST(req: Request) { record.type, record_name, record.content, + record.zone_name, 1, ); if (user_record && user_record.length > 0) { diff --git a/app/api/record/admin/apply/route.ts b/app/api/record/admin/apply/route.ts index a23c3c8..6ebb320 100644 --- a/app/api/record/admin/apply/route.ts +++ b/app/api/record/admin/apply/route.ts @@ -20,7 +20,6 @@ export async function POST(req: Request) { const { record: reviewRecord, userId, id } = await req.json(); const record = { ...reviewRecord, - // comment: "Created by wr.do (review mode)", id, }; @@ -40,9 +39,11 @@ export async function POST(req: Request) { record, ); + console.log("[data]", data); + if (!data.success || !data.result?.id) { - return Response.json(data.messages, { - status: 501, + return Response.json(data.errors[0].message, { + status: 503, }); } else { const res = await updateUserRecordReview(userId, id, { diff --git a/app/api/record/delete/route.ts b/app/api/record/delete/route.ts index e2bdebf..155f14e 100644 --- a/app/api/record/delete/route.ts +++ b/app/api/record/delete/route.ts @@ -28,14 +28,22 @@ export async function POST(req: Request) { }); } - const res = await deleteDNSRecord( - matchedZone.cf_zone_id!, - matchedZone.cf_api_key!, - matchedZone.cf_email!, - record_id, - ); + if (active !== 3) { + const res = await deleteDNSRecord( + matchedZone.cf_zone_id!, + matchedZone.cf_api_key!, + matchedZone.cf_email!, + record_id, + ); - if (res && res.result?.id) { + if (res && res.result?.id) { + await deleteUserRecord(user.id, record_id, zone_id, active); + return Response.json("success", { + status: 200, + statusText: "success", + }); + } + } else { await deleteUserRecord(user.id, record_id, zone_id, active); return Response.json("success", { status: 200, diff --git a/app/manifest.json b/app/manifest.json index 54f6e8b..b33d2a2 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.1", + "versionName": "1.0.2", "versionCode": "1", "start_url": "/", "orientation": "portrait", diff --git a/auth.config.ts b/auth.config.ts index 3d17fb1..0b84f1a 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -1,4 +1,5 @@ import type { NextAuthConfig } from "next-auth"; +import Credentials from "next-auth/providers/credentials"; import Github from "next-auth/providers/github"; import Google from "next-auth/providers/google"; import Resend from "next-auth/providers/resend"; @@ -67,5 +68,29 @@ export default { }, }), linuxDoProvider, + Credentials({ + name: "Credentials", + credentials: { + name: { label: "name", type: "text" }, + email: { label: "Email", type: "email" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + const res = await fetch( + process.env.AUTH_URL + "/api/auth/credentials", + { + method: "POST", + body: JSON.stringify(credentials), + }, + ); + // console.log("[res]", res); + + if (res.ok) { + return res.json(); + } + + return null; + }, + }), ], } satisfies NextAuthConfig; diff --git a/auth.ts b/auth.ts index 8421fbe..a657a09 100644 --- a/auth.ts +++ b/auth.ts @@ -53,7 +53,6 @@ export const { return session; }, - async jwt({ token }) { if (!token.sub) return token; diff --git a/components/forms/record-form.tsx b/components/forms/record-form.tsx index f908422..bb445ed 100644 --- a/components/forms/record-form.tsx +++ b/components/forms/record-form.tsx @@ -65,9 +65,7 @@ export function RecordForm({ const [currentRecordType, setCurrentRecordType] = useState( initData?.type || "CNAME", ); - const [currentZoneName, setCurrentZoneName] = useState( - initData?.zone_name || "wr.do", - ); + const [currentZoneName, setCurrentZoneName] = useState(initData?.zone_name); const [email, setEmail] = useState(initData?.user.email || user.email); const [allowedRecordTypes, setAllowedRecordTypes] = useState([]); const isAdmin = action.indexOf("admin") > -1; @@ -83,7 +81,7 @@ export function RecordForm({ } = useForm({ resolver: zodResolver(createRecordSchema), defaultValues: { - zone_name: initData?.zone_name || "wr.do", + zone_name: initData?.zone_name, type: initData?.type || "CNAME", ttl: initData?.ttl || 1, proxied: initData?.proxied || false, @@ -121,6 +119,7 @@ export function RecordForm({ useEffect(() => { if (validDefaultDomain) { + setValue("zone_name", validDefaultDomain); setCurrentZoneName(validDefaultDomain); } }, [validDefaultDomain]); @@ -563,7 +562,13 @@ export function RecordForm({ {isPending ? ( ) : ( -

{type === "edit" ? t("Update") : t("Save")}

+

+ {type === "edit" + ? initData?.active === 2 && isAdmin + ? t("Agree") + : t("Update") + : t("Save")} +

)} )} diff --git a/components/forms/url-form.tsx b/components/forms/url-form.tsx index 9c4fc0b..a3e0231 100644 --- a/components/forms/url-form.tsx +++ b/components/forms/url-form.tsx @@ -1,6 +1,12 @@ "use client"; -import { Dispatch, SetStateAction, useMemo, useTransition } from "react"; +import { + Dispatch, + SetStateAction, + useEffect, + useMemo, + useTransition, +} from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { User } from "@prisma/client"; import { Sparkles } from "lucide-react"; @@ -58,7 +64,6 @@ export function UrlForm({ handleSubmit, register, formState: { errors }, - getValues, setValue, } = useForm({ resolver: zodResolver(createUrlSchema), @@ -67,7 +72,7 @@ export function UrlForm({ target: initData?.target || "", url: initData?.url || "", active: initData?.active || 1, - prefix: initData?.prefix || "wr.do", + prefix: initData?.prefix || "", visible: initData?.visible || 0, expiration: initData?.expiration || "-1", password: initData?.password || "", @@ -96,6 +101,12 @@ export function UrlForm({ return shortDomains[0].domain_name; }, [shortDomains, initData?.prefix]); + useEffect(() => { + if (validDefaultDomain) { + setValue("prefix", validDefaultDomain); + } + }, [validDefaultDomain]); + const onSubmit = handleSubmit((data) => { if (type === "add") { handleCreateUrl(data); diff --git a/components/forms/user-api-key-form.tsx b/components/forms/user-api-key-form.tsx index 9c2d27e..aa7cf75 100644 --- a/components/forms/user-api-key-form.tsx +++ b/components/forms/user-api-key-form.tsx @@ -68,6 +68,7 @@ export function UserApiKeyForm({ user }: UserNameFormProps) { { type?: string; } type FormData = z.infer; +type FormData2 = z.infer; export function UserAuthForm({ className, type, ...props }: UserAuthFormProps) { const { @@ -34,34 +35,63 @@ export function UserAuthForm({ className, type, ...props }: UserAuthFormProps) { } = useForm({ resolver: zodResolver(userAuthSchema), }); - const [isLoading, setIsLoading] = React.useState(false); + const { + register: register2, + handleSubmit: handleSubmit2, + formState: { errors: errors2 }, + } = useForm({ + resolver: zodResolver(userPasswordAuthSchema), + }); + // const [isLoading, setIsLoading] = React.useState(false); + const [isLoading, startTransition] = React.useTransition(); const [isGoogleLoading, setIsGoogleLoading] = React.useState(false); const [isGithubLoading, setIsGithubLoading] = React.useState(false); const [isLinuxDoLoading, setIsLinuxDoLoading] = React.useState(false); const searchParams = useSearchParams(); + const router = useRouter(); const t = useTranslations("Auth"); async function onSubmit(data: FormData) { - setIsLoading(true); - - const signInResult = await signIn("resend", { - email: data.email.toLowerCase(), - redirect: false, - callbackUrl: searchParams?.get("from") || "/dashboard", - }); - - setIsLoading(false); - - if (!signInResult?.ok) { - return toast.error("Something went wrong.", { - description: "Your sign in request failed. Please try again.", + startTransition(async () => { + const signInResult = await signIn("resend", { + email: data.email.toLowerCase(), + redirect: false, + callbackUrl: searchParams?.get("from") || "/dashboard", }); - } - return toast.success("Check your email", { - description: "We sent you a login link. Be sure to check your spam too.", + if (!signInResult?.ok) { + toast.error(t("Something went wrong"), { + description: "Your sign in request failed. Please try again.", + }); + } + + toast.success(t("Check your email"), { + description: `${t("We sent you a login link")}. ${t("Be sure to check your spam too")}.`, + }); + }); + } + async function onSubmitPwd(data: FormData2) { + startTransition(async () => { + const signInResult = await signIn("credentials", { + name: data.name, + email: data.email, + password: data.password, + redirect: false, + callbackUrl: searchParams?.get("from") || "/dashboard", + }); + + // console.log("[signInResult]", signInResult); + + if (signInResult?.error) { + toast.error(t("Something went wrong"), { + description: `[${signInResult?.error}] ${t("Incorrect email or password")}.`, + }); + } else { + toast.success(t("Welcome back!")); + router.push(searchParams?.get("from") || "/dashboard"); + } }); } @@ -99,12 +129,127 @@ export function UserAuthForm({ className, type, ...props }: UserAuthFormProps) { ); } + const rendeResend = () => + loginMethod["resend"] && ( +
+
+
+ + + {errors?.email && ( +

+ {errors.email.message} +

+ )} +
+ +
+
+ ); + + const rendeCredentials = () => + loginMethod["credentials"] && ( +
+
+
+ + + {errors2?.email && ( +

+ {errors2.email.message} +

+ )} +
+
+ + + {errors2?.password && ( +

+ {errors2.password.message} +

+ )} +
+ + + +

+ 📢 {t("Unregistered users will automatically create an account")}. +

+
+
+ ); + return (
+ {!loginMethod.registration && ( +

+ 📢 {t("Administrator has disabled new user registration")}. +

+ )} + {loginMethod["google"] && ( - + )} {loginMethod["github"] && ( - + )} {loginMethod["linuxdo"] && ( - + )} {(loginMethod["google"] || loginMethod["github"] || loginMethod["linuxdo"]) && - loginMethod["resend"] && + (loginMethod["resend"] || loginMethod["credentials"]) && rendeSeparator()} - {loginMethod["resend"] && ( -
-
-
- - - {errors?.email && ( -

- {errors.email.message} -

- )} -
- -
-
+ {loginMethod["resend"] && loginMethod["credentials"] ? ( + + + {t("Email Code")} + {t("Password")} + + {rendeResend()} + {rendeCredentials()} + + ) : ( + <> + {rendeResend()} + {rendeCredentials()} + )}
); diff --git a/components/forms/user-password-form.tsx b/components/forms/user-password-form.tsx new file mode 100644 index 0000000..417aaaf --- /dev/null +++ b/components/forms/user-password-form.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { + updateUserPassword, + type FormData, +} from "@/actions/update-user-password"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { User } from "@prisma/client"; +import { useSession } from "next-auth/react"; +import { useTranslations } from "next-intl"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; + +import { userPasswordSchema } from "@/lib/validations/user"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { SectionColumns } from "@/components/dashboard/section-columns"; +import { Icons } from "@/components/shared/icons"; + +interface UserPasswordFormProps { + user: Pick; +} + +export function UserPasswordForm({ user }: UserPasswordFormProps) { + const { update } = useSession(); + const [updated, setUpdated] = useState(false); + const [isPending, startTransition] = useTransition(); + const updateUserPasswordWithId = updateUserPassword.bind(null, user.id); + + const t = useTranslations("Setting"); + + const checkUpdate = (value: string) => { + setUpdated(value !== ""); + }; + + const { + handleSubmit, + register, + formState: { errors }, + } = useForm({ + resolver: zodResolver(userPasswordSchema), + defaultValues: { + password: "", + }, + }); + + const onSubmit = handleSubmit((data) => { + startTransition(async () => { + const { status } = await updateUserPasswordWithId(data); + + if (status !== "success") { + toast.error("Something went wrong.", { + description: "Your password was not updated. Please try again.", + }); + } else { + await update(); + setUpdated(false); + toast.success("Your password has been updated."); + } + }); + }); + + return ( +
+ +
+ + checkUpdate(e.target.value)} + /> + +
+
+ {errors?.password && ( +

+ {errors.password.message} +

+ )} +

+ {t("At least 6 characters, Max 32 characters")} +

+
+
+
+ ); +} diff --git a/content/docs/developer/deploy-zh.mdx b/content/docs/developer/deploy-zh.mdx index cf7997c..902cffa 100644 --- a/content/docs/developer/deploy-zh.mdx +++ b/content/docs/developer/deploy-zh.mdx @@ -11,7 +11,7 @@ description: 选择你的部署方式 ## 使用 Vercel 部署(推荐) -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo&env=DATABASE_URL&env=AUTH_SECRET&env=RESEND_API_KEY&env=NEXT_PUBLIC_EMAIL_R2_DOMAIN&env=GITHUB_TOKEN) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo) ## 使用 Docker Compose 部署 diff --git a/content/docs/developer/deploy.mdx b/content/docs/developer/deploy.mdx index 134c970..76171d6 100644 --- a/content/docs/developer/deploy.mdx +++ b/content/docs/developer/deploy.mdx @@ -12,7 +12,7 @@ description: Choose your deployment method ## Deploy with Vercel (Recommended) -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo&env=DATABASE_URL&env=AUTH_SECRET&env=RESEND_API_KEY&env=NEXT_PUBLIC_EMAIL_R2_DOMAIN&env=GITHUB_TOKEN) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo) Remember to fill in the necessary environment variables. diff --git a/content/docs/developer/installation-zh.mdx b/content/docs/developer/installation-zh.mdx index 126a674..58d2aee 100644 --- a/content/docs/developer/installation-zh.mdx +++ b/content/docs/developer/installation-zh.mdx @@ -12,6 +12,7 @@ description: 简单介绍 WR.DO 部署所需的环境变量 或参考社区优秀部署文档: - https://linux.do/t/topic/711806 + - https://bravexist.cn/2025/06/wr.do.html diff --git a/content/docs/developer/installation.mdx b/content/docs/developer/installation.mdx index 0d5e341..4b93305 100644 --- a/content/docs/developer/installation.mdx +++ b/content/docs/developer/installation.mdx @@ -12,6 +12,7 @@ description: How to install the project. Or read unofficial deployment tutorials: - https://linux.do/t/topic/711806 + - https://bravexist.cn/2025/06/wr.do.html @@ -26,7 +27,7 @@ npx create-next-app wrdo --example "https://github.com/oiov/wr.do" Or deploy with Vercel : -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Foiov%2Fwr.do) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oiov/wr.do.git&project-name=wrdo) A good way to create your repository, but the deployment will fail because you diff --git a/content/docs/developer/quick-start-zh.mdx b/content/docs/developer/quick-start-zh.mdx index b81e4d7..7c17f99 100644 --- a/content/docs/developer/quick-start-zh.mdx +++ b/content/docs/developer/quick-start-zh.mdx @@ -193,27 +193,7 @@ pnpm dev 通过浏览器访问:[http://localhost:3000](http://localhost:3000) -## 7. 设置系统 - -#### 创建第一个账号并将账号权限更改为 ADMIN - -请按以下步骤操作: - -* 1. 通过 [http://localhost:3000/login](http://localhost:3000/login) 注册登录你的第一个账号; -* 2. 通过 [http://localhost:3000/setup](http://localhost:3000/setup) 将账号权限更改为 ADMIN; -* 3. 然后根据 **面板引导** 配置系统并添加第一个域名。 - -![](/_static/docs/setup-1.png) - -![](/_static/docs/setup-2.png) - - - 将账号权限更改为 ADMIN 后,你可以刷新页面并访问 http://localhost:3000/admin。 - -你必须至少添加一个域名,才能使用短链接、邮件或子域名管理等功能。 - - -## 8. 部署 +## 7. 部署教程 详见:[部署指南](/docs/developer/deploy) @@ -227,12 +207,4 @@ pnpm dev https://dash.cloudflare.com/[account_id]/[zone_name]/ssl-tls/configuration ``` -将 `SSL/TLS 加密模式` 更改为 `Full` 模式。 - -### 如何修改团队计划配额? - -通过 team.ts 文件修改: - -```bash -https://github.com/oiov/wr.do/tree/main/config/team.ts -``` \ No newline at end of file +将 `SSL/TLS 加密模式` 更改为 `Full` 模式。 \ No newline at end of file diff --git a/content/docs/developer/quick-start.mdx b/content/docs/developer/quick-start.mdx index d422b02..9cd0c9d 100644 --- a/content/docs/developer/quick-start.mdx +++ b/content/docs/developer/quick-start.mdx @@ -182,27 +182,7 @@ pnpm dev ``` Via [http://localhost:3000](http://localhost:3000) -## 7. Setup System - -#### Create the first account and Change the account's role to ADMIN - -Follow the steps below: - -- 1. Via [http://localhost:3000/login](http://localhost:3000/login), login with your account. -- 2. Via [http://localhost:3000/setup](http://localhost:3000/setup), change the account's role to ADMIN. -- 3. Then follow the **panel guide** to config the system and add the first domain. - -![](/_static/docs/setup-1.png) - -![](/_static/docs/setup-2.png) - - - After change the account's role to ADMIN, then you can refresh the website and access http://localhost:3000/admin. - - You must add at least one domain to start using short links, email or subdomain management features. - - -## 8. Deploy +## 7. Deploy See [Deploy Guide](/docs/developer/deploy). @@ -216,12 +196,4 @@ Via: https://dash.cloudflare.com/[account_id]/[zone_name]/ssl-tls/configuration ``` -Change the `SSL/TLS Encryption` Mode to `Full` in the Cloudflare dashboard. - -### How can I change the team plan quota? - -Via team.ts: - -```bash -https://github.com/oiov/wr.do/tree/main/config/team.ts -``` +Change the `SSL/TLS Encryption` Mode to `Full` in the Cloudflare dashboard. \ No newline at end of file diff --git a/lib/cloudflare.ts b/lib/cloudflare.ts index a2d2ddb..eb503bc 100644 --- a/lib/cloudflare.ts +++ b/lib/cloudflare.ts @@ -77,9 +77,11 @@ export const createDNSRecord = async ( body: JSON.stringify(record), }); - if (!response.ok) { - throw new Error(`HTTP error status: ${response.status}`); - } + // console.log("response.status", await response.json()); + + // if (!response.ok) { + // throw new Error(`HTTP error status: ${response.status}`); + // } const data = await response.json(); return data; @@ -110,10 +112,6 @@ export const deleteDNSRecord = async ( headers, }); - if (!response.ok) { - throw new Error(`HTTP error status: ${response.status}`); - } - const data = await response.json(); return data; } catch (error) { diff --git a/lib/dto/cloudflare-dns-record.ts b/lib/dto/cloudflare-dns-record.ts index 8bc2070..8811277 100644 --- a/lib/dto/cloudflare-dns-record.ts +++ b/lib/dto/cloudflare-dns-record.ts @@ -103,7 +103,7 @@ export async function updateUserRecordReview( const res = await prisma.userRecord.update({ where: { - record_id, + id, }, data: { userId, @@ -234,6 +234,7 @@ export async function getUserRecordByTypeNameContent( type: string, name: string, content: string, + zone_name: string, active: number = 1, ) { return await prisma.userRecord.findMany({ @@ -242,6 +243,7 @@ export async function getUserRecordByTypeNameContent( type, // content, name, + zone_name, active: { not: 3, }, diff --git a/lib/validations/auth.ts b/lib/validations/auth.ts index dc383e6..6178860 100644 --- a/lib/validations/auth.ts +++ b/lib/validations/auth.ts @@ -5,6 +5,12 @@ export const userAuthSchema = z.object({ email: z.string().email(), }); +export const userPasswordAuthSchema = z.object({ + name: z.string().optional(), + email: z.string().email(), + password: z.string().min(6), +}); + export const updateUserSchema = z.object({ email: z.string().email(), image: z.string(), diff --git a/lib/validations/user.ts b/lib/validations/user.ts index 26296da..c9fb423 100644 --- a/lib/validations/user.ts +++ b/lib/validations/user.ts @@ -9,4 +9,8 @@ export const userRoleSchema = z.object({ role: z.nativeEnum(UserRole), }); +export const userPasswordSchema = z.object({ + password: z.string().min(6).max(32), +}); + export const userApiKeySchema = z.object({}); diff --git a/locales/en.json b/locales/en.json index db41e53..983a9ef 100644 --- a/locales/en.json +++ b/locales/en.json @@ -19,7 +19,8 @@ "Domain Name": "Domain Name", "Please enter a valid domain name (must be hosted on Cloudflare)": "Please enter a valid domain name (must be hosted on Cloudflare)", "Or add later": "Or add later", - "Submit": "Submit" + "Submit": "Submit", + "After v1-0-2, this setup guide is not needed anymore": "After v1.0.2, this setup guide is not needed anymore" }, "List": { "Short URLs": "Short URLs", @@ -73,9 +74,8 @@ "per page": "per page", "Total Subdomains": "Total Subdomains", "Subdomain List": "Subdomains", - "Please read the": "Please read the", + "Before using please read the": "Before using please read the", "Legitimacy review": "Legitimacy review", - "before using": "before using", "See": "See", "examples": "examples", "for more usage": "for more usage", @@ -166,7 +166,8 @@ "Plan name must be unique": "Plan name must be unique", "Record Types": "Record Types", "Allowed record types": "Allowed record types", - "use `,` to separate": "use `,` to separate" + "use `,` to separate": "use `,` to separate", + "Agree": "Agree" }, "Components": { "Dashboard": "Dashboard", @@ -334,7 +335,18 @@ "Or continue with": "Or continue with", "Email": "Email", "Sign Up with Email": "Sign Up with Email", - "Sign In with Email": "Sign In with Email" + "Sign In with Email": "Sign In with Email", + "Email Code": "Email", + "Password": "Password", + "Sign In / Sign Up": "Sign In / Sign Up", + "Incorrect email or password": "Incorrect email or password", + "Something went wrong": "Something went wrong", + "Check your email": "Check your email", + "We sent you a login link": "We sent you a login link", + "Be sure to check your spam too": "Be sure to check your spam too", + "Welcome back!": "Welcome back!", + "Unregistered users will automatically create an account": "Unregistered users will automatically create an account", + "Administrator has disabled new user registration": "Administrator has disabled new user registration" }, "System": { "MENU": "Menu", @@ -466,6 +478,10 @@ "Set system notification, this will be displayed in the header": "Set system notification, this will be displayed in the header", "Login Methods": "Login Methods", "Select the login methods that users can use to log in": "Select the login methods that users can use to log in", - "Resend Email": "Resend Email" + "Resend Email": "Resend Email", + "Email Password": "Email Password", + "Your Password": "Your Password", + "Update your password": "Update your password", + "At least 6 characters, Max 32 characters": "At least 6 characters, Max 32 characters" } } diff --git a/locales/zh.json b/locales/zh.json index 12f92fc..c94ef8e 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -19,7 +19,8 @@ "Domain Name": "域名", "Please enter a valid domain name (must be hosted on Cloudflare)": "请输入有效的域名(确保已经托管到 Cloudflare)", "Or add later": "或稍后添加", - "Submit": "提交" + "Submit": "提交", + "After v1-0-2, this setup guide is not needed anymore": "此初始化引导在 v1.0.2 版本后, 不再是必要步骤" }, "List": { "Short URLs": "短链列表", @@ -73,9 +74,8 @@ "per page": "条/页", "Total Subdomains": "总计", "Subdomain List": "子域名列表", - "Please read the": "请阅读", + "Before using please read the": "在使用之前请阅读", "legitimacy review": "链接合法性审查", - "before using": "在使用之前", "See": "查看", "examples": "示例", "for more usage": "了解更多用法", @@ -166,7 +166,8 @@ "Plan name must be unique": "计划名称必须唯一", "Record Types": "DNS 记录类型", "Allowed record types": "请填写标准的 DNS 记录类型", - "use `,` to separate": "使用 `,` 分隔" + "use `,` to separate": "使用 `,` 分隔", + "Agree": "同意" }, "Components": { "Dashboard": "用户面板", @@ -334,7 +335,18 @@ "Or continue with": "或使用", "Email": "邮箱", "Sign Up with Email": "使用邮箱注册", - "Sign In with Email": "使用邮箱登录" + "Sign In with Email": "使用邮箱登录", + "Email Code": "邮箱验证", + "Password": "账号密码", + "Sign In / Sign Up": "点击登录/注册", + "Incorrect email or password": "邮箱或密码错误", + "Something went wrong": "出错了", + "Check your email": "检查您的邮箱", + "We sent you a login link": "我们已向您发送登录链接", + "Be sure to check your spam too": "请确保检查您的垃圾邮件", + "Welcome back!": "欢迎回来!", + "Unregistered users will automatically create an account": "未注册用户将自动创建账户", + "Administrator has disabled new user registration": "管理员已关闭新用户注册" }, "System": { "MENU": "菜单", @@ -466,6 +478,10 @@ "Set system notification, this will be displayed in the header": "设置系统通知,将在网页顶部显示", "Login Methods": "登录方式", "Select the login methods that users can use to log in": "选择用户可以使用的登录方式", - "Resend Email": "Resend 邮箱登录" + "Resend Email": "Resend 邮箱登录", + "Email Password": "账号密码登录", + "Your Password": "账号密码", + "Update your password": "更新您的密码", + "At least 6 characters, Max 32 characters": "密码长度至少6位,最多32位" } } diff --git a/package.json b/package.json index 2e011f8..90e1a1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wr.do", - "version": "1.0.1", + "version": "1.0.2", "author": { "name": "oiov", "url": "https://github.com/oiov" @@ -75,6 +75,7 @@ "@vercel/analytics": "^1.3.1", "@vercel/functions": "^1.4.0", "@vercel/og": "^0.6.2", + "bcrypt": "^6.0.0", "chalk": "^4.1.1", "cheerio": "1.0.0-rc.12", "class-variance-authority": "^0.7.0", @@ -140,6 +141,7 @@ "@ianvs/prettier-plugin-sort-imports": "^4.3.1", "@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/typography": "^0.5.13", + "@types/bcrypt": "^5.0.2", "@types/lodash": "^4.17.16", "@types/node": "^20.14.11", "@types/qrcode": "^1.5.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c31cdc6..e88d239 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,9 @@ importers: '@vercel/og': specifier: ^0.6.2 version: 0.6.2 + bcrypt: + specifier: ^6.0.0 + version: 6.0.0 chalk: specifier: ^4.1.1 version: 4.1.2 @@ -348,6 +351,9 @@ importers: '@tailwindcss/typography': specifier: ^0.5.13 version: 0.5.13(tailwindcss@3.4.6) + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 '@types/lodash': specifier: ^4.17.16 version: 4.17.16 @@ -3145,6 +3151,9 @@ packages: '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + '@types/conventional-commits-parser@5.0.0': resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} @@ -3808,6 +3817,10 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + bcrypt@6.0.0: + resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} + engines: {node: '>= 18'} + big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} @@ -6171,6 +6184,14 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@8.4.0: + resolution: {integrity: sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==} + engines: {node: ^18 || ^20 || >= 21} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} @@ -11105,6 +11126,10 @@ snapshots: dependencies: '@types/estree': 1.0.5 + '@types/bcrypt@5.0.2': + dependencies: + '@types/node': 20.14.11 + '@types/conventional-commits-parser@5.0.0': dependencies: '@types/node': 20.14.11 @@ -11926,6 +11951,11 @@ snapshots: base64id@2.0.0: {} + bcrypt@6.0.0: + dependencies: + node-addon-api: 8.4.0 + node-gyp-build: 4.8.4 + big.js@5.2.2: {} binary-extensions@2.2.0: {} @@ -14885,6 +14915,10 @@ snapshots: lower-case: 2.0.2 tslib: 2.8.1 + node-addon-api@8.4.0: {} + + node-gyp-build@4.8.4: {} + node-releases@2.0.14: {} node-releases@2.0.18: {} diff --git a/prisma/migrations/20250617100233/migration.sql b/prisma/migrations/20250617100233/migration.sql new file mode 100644 index 0000000..97e192e --- /dev/null +++ b/prisma/migrations/20250617100233/migration.sql @@ -0,0 +1,38 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "password" TEXT; + +INSERT INTO "system_configs" + ( + "key", + "value", + "type", + "description" + ) +VALUES + ( + 'enable_email_password_login', + 'true', + 'BOOLEAN', + '是否启用邮箱密码登录' +); + +INSERT INTO "users" + ( + id, + name, + email, + password, + active, + role, + team + ) +VALUES + ( + 'cmadvu9w874j2sczhg174pftq', + 'admin', + 'admin@admin.com', + '$2b$10$FQIPnvwTQ2dwL2F3SIiKDOf.qTvMcwfc0KsbqHQBWflpFT2o8Uwji', + 1, + 'ADMIN', + 'free' +); \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 917dfb7..993f88e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -61,6 +61,7 @@ model User { createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @default(now()) @map(name: "updated_at") role UserRole @default(USER) + password String? accounts Account[] sessions Session[] diff --git a/public/manifest.json b/public/manifest.json index 54f6e8b..b33d2a2 100644 --- a/public/manifest.json +++ b/public/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.1", + "versionName": "1.0.2", "versionCode": "1", "start_url": "/", "orientation": "portrait", diff --git a/public/site.webmanifest b/public/site.webmanifest index 54f6e8b..b33d2a2 100644 --- a/public/site.webmanifest +++ b/public/site.webmanifest @@ -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.1", + "versionName": "1.0.2", "versionCode": "1", "start_url": "/", "orientation": "portrait", diff --git a/public/sw.js.map b/public/sw.js.map index 5a8c061..85103de 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/6ce495a00cb1e0e48fb7047c3c68904a/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../../private/var/folders/9b/3qmyp8zd2xvdspdrp149fyg00000gn/T/77937c383c2f0f6b52d02a41dcd1ccc9/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-routing@6.6.0/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-strategies@6.6.0/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/songjunxi/Desktop/repos/wrdo-app/wr.do/node_modules/.pnpm/workbox-core@6.6.0/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file