refact: remove /s for short link

This commit is contained in:
oiov
2025-09-14 16:10:43 +08:00
parent a43135f088
commit 044356ad29
19 changed files with 78 additions and 142 deletions

View File

@@ -177,7 +177,7 @@ See [How to Trigger Sync](https://wr.do/docs/developer/sync) for details.
- Discord: https://discord.gg/AHPQYuZu3m
- 微信群:
<img width="300" src="https://wr.do/s/group" />
<img width="300" src="https://wr.do/group" />
## Contributors

View File

@@ -214,7 +214,7 @@ pnpm dev
- Discord: https://discord.gg/AHPQYuZu3m
- 微信群:
<img width="300" src="https://wr.do/s/group" />
<img width="300" src="https://wr.do/group" />
## 贡献者

View File

@@ -98,7 +98,7 @@ const RealtimeLogs = ({
<div className="flex items-center gap-1">
<Link
className="text-sm font-semibold"
href={`https://${loc.userUrl?.prefix}/s/${loc.userUrl?.url}`}
href={`https://${loc.userUrl?.prefix}/${loc.userUrl?.url}`}
target="_blank"
>
{loc.userUrl?.url}

View File

@@ -7,8 +7,8 @@ import { DashboardHeader } from "@/components/dashboard/header";
import UserUrlsList from "./url-list";
export const metadata = constructMetadata({
title: "Short URLs",
description: "List and manage records.",
title: "Links",
description: "List and manage short links.",
});
export default async function DashboardPage() {

View File

@@ -345,7 +345,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
<TableCell className="col-span-1 flex items-center gap-1 sm:col-span-2">
<Link
className="overflow-hidden text-ellipsis whitespace-normal text-slate-600 hover:text-blue-400 hover:underline dark:text-slate-400"
href={`https://${short.prefix}/s/${short.url}${short.password ? `?password=${short.password}` : ""}`}
href={`https://${short.prefix}/${short.url}${short.password ? `?password=${short.password}` : ""}`}
target="_blank"
prefetch={false}
title={short.url}
@@ -353,7 +353,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
{short.url}
</Link>
<CopyButton
value={`${short.prefix}/s/${short.url}${short.password ? `?password=${short.password}` : ""}`}
value={`${short.prefix}/${short.url}${short.password ? `?password=${short.password}` : ""}`}
className={cn(
"size-[25px]",
"duration-250 transition-all group-hover:opacity-100",
@@ -495,7 +495,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
<div className="flex items-center">
<Link
className="overflow-hidden text-ellipsis whitespace-normal text-sm font-semibold text-slate-600 hover:text-blue-400 hover:underline dark:text-slate-300"
href={`https://${short.prefix}/s/${short.url}${short.password ? `?password=${short.password}` : ""}`}
href={`https://${short.prefix}/${short.url}${short.password ? `?password=${short.password}` : ""}`}
target="_blank"
prefetch={false}
title={short.url}
@@ -503,7 +503,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
{short.url}
</Link>
<CopyButton
value={`${short.prefix}/s/${short.url}${short.password ? `?password=${short.password}` : ""}`}
value={`https://${short.prefix}/${short.url}${short.password ? `?password=${short.password}` : ""}`}
className={cn(
"size-[25px]",
"duration-250 transition-all group-hover:opacity-100",
@@ -759,7 +759,7 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
{selectedUrl && (
<QRCodeEditor
user={{ id: user.id, apiKey: user.apiKey || "", team: user.team! }}
url={`https://${selectedUrl.prefix}/s/${selectedUrl.url}`}
url={`https://${selectedUrl.prefix}/${selectedUrl.url}`}
/>
)}
</Modal>

View File

@@ -27,7 +27,7 @@ export async function POST(request: NextRequest) {
const orderedResults = ids.map((id) => {
const item = dataMap.get(id);
return item ? `${item.prefix}/s/${item.url}` : "";
return item ? `${item.prefix}/${item.url}` : "";
});
return NextResponse.json({

View File

@@ -21,7 +21,7 @@ export async function POST(request: NextRequest) {
const orderedResults = ids.map((id) => {
const item = dataMap.get(id);
return item ? `${item.prefix}/s/${item.url}` : "";
return item ? `${item.prefix}/${item.url}` : "";
});
return NextResponse.json({

View File

@@ -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.1.4",
"versionName": "1.1.5",
"versionCode": "1",
"start_url": "/",
"orientation": "portrait",

View File

@@ -61,7 +61,7 @@ export default function PasswordPrompt() {
e.preventDefault();
const fullPassword = password.join("");
if (slug && !isPending && fullPassword.length === 6) {
router.push(`/s/${slug}?password=${encodeURIComponent(fullPassword)}`);
router.push(`/${slug}?password=${encodeURIComponent(fullPassword)}`);
}
});
};

View File

@@ -295,7 +295,7 @@ export function UrlForm({
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
{t("A random url suffix")}. {t("Final url like")}
wr.do/s/suffix
wr.do/suffix
</p>
)}
</div>

View File

@@ -66,7 +66,7 @@ export default function UrlShotenerExp() {
<div>
<div className="flex items-center gap-2">
<p className="text-base font-bold text-gray-700 dark:text-gray-50">
wr.do/s/try
wr.do/try
</p>
<div className="flex gap-1">
<button className="rounded-full border p-1.5 transition-colors hover:bg-gray-100 dark:bg-gray-600/50">

View File

@@ -66,44 +66,4 @@ docker pull ghcr.io/oiov/wr.do/wrdo:latest
## 打包镜像
Fork 此仓库后,在 Actions 中触发打包镜像。
## 付费部署服务
**联系方式:** 微信 `oiovdev`
本项目提供专业的代部署服务,根据不同需求灵活收费。
### 部署方案与价格
| 部署方式 | 配置说明 | 服务费用 |
| --- | --- | --- |
| Vercel | 应用托管 + 自建数据库 + 域名配置 | ¥500 |
| Vercel | 应用托管 + Neon 云数据库 + 域名配置 | ¥400 |
| Docker | 服务器部署 + 自建数据库 + 域名配置 | ¥500 |
| Docker | 服务器部署 + Neon 云数据库 + 域名配置 | ¥450 |
### 重要说明
- **数据库要求:** 自建数据库需要准备服务器
- **域名要求:** 至少准备一个域名,并且需要托管到 Cloudflare
- **服务器与域名费用需另付:** 服务器购买和域名注册费用不包含在上述服务费中,建议您提前自行准备
- **默认环境配置:** 服务器部署默认安装宝塔面板进行管理
> 推荐云服务器 RackNerd美国免备案vps2核2G 配置仅需20.98$≈145RMB/年,支持支付宝付款,[💁点击优惠链接直达](https://wr.do/s/20u) 。
> 推荐域名注册商 [NameSilo](https://www.namesilo.com/?rid=50fae21ln), 新用户使用优惠码下单可以减 1$,优惠码: wrdo
### 准备工作
在开始部署前,请提前注册以下平台账户:
- **Cloudflare**https://dash.cloudflare.com (必须,域名管理服务)
- **Resend**https://resend.com (必须,邮件发送服务)
- **Vercel**https://vercel.com (可选,应用部署平台)
- **Neon**https://neon.tech (可选,云数据库服务)
### 联系方式
如需部署服务,请添加微信 `oiovdev` 详细咨询,将根据您的具体需求提供定制化的部署方案。
Fork 此仓库后,在 Actions 中触发打包镜像。

View File

@@ -66,43 +66,4 @@ Find the official image here: [container/wr.do](https://github.com/oiov/wr.do/pk
## Build Image
Fork this repository and trigger the build image action in Actions.
## Paid Deployment Service
**Contact:** WeChat `oiovdev`
This project offers professional deployment services with flexible pricing based on different requirements.
### Deployment Plans & Pricing
| Deployment Method | Configuration | Service Fee |
| --- | --- | --- |
| Vercel | App hosting + Self-hosted database + Domain configuration | ¥500 |
| Vercel | App hosting + Neon cloud database + Domain configuration | ¥400 |
| Docker | Server deployment + Self-hosted database + Domain configuration | ¥500 |
| Docker | Server deployment + Neon cloud database + Domain configuration | ¥450 |
### Important Notes
- **Database Requirements:** Self-hosted database requires server preparation
- **Domain Requirements:** At least one domain required, must be managed through Cloudflare
- **Server & Domain Costs Not Included:** Server purchase and domain registration fees are not included in the above service fees. Please prepare them in advance
- **Default Environment Configuration:** Server deployment includes aaPanel (BaoTa) installation by default for management
> **Recommended Cloud Server:** RackNerd, US-based VPS (no ICP filing required), 2 cores 2GB configuration for only $20.98≈¥145/year, supports Alipay payment. [💁Click here for discount link](https://wr.do/s/20u)
> **Recommended Domain Registrar:** [NameSilo](https://wr.do/s/domain), new users can save $1 with coupon code: **wrdo**
### Prerequisites
Please register accounts on the following platforms before deployment:
- **Cloudflare**: https://dash.cloudflare.com (Required, domain management service)
- **Resend**: https://resend.com (Required, email delivery service)
- **Vercel**: https://vercel.com (Optional, app deployment platform)
- **Neon**: https://neon.tech (Optional, cloud database service)
### Contact Information
For deployment services, please add WeChat `oiovdev` for detailed consultation. We will provide customized deployment solutions based on your specific needs.
Fork this repository and trigger the build image action in Actions.

View File

@@ -21,20 +21,70 @@ const redirectMap = {
"IncorrectPassword[0005]": "/password-prompt?error=1&slug=",
};
const systemRoutes = [
"/docs",
"/dashboard",
"/admin",
"/pricing",
"/plan",
"/privacy",
"/terms",
"/auth",
"/login",
"/register",
"/emails",
"/link-status",
"/password-prompt",
"/chat",
"/manifest.json",
"/robots.txt",
"/opengraph-image.jpg",
"/favicon.ico",
];
async function handleShortUrl(req: NextAuthRequest) {
if (!req.url.includes("/s/")) return NextResponse.next();
const url = new URL(req.url);
const pathname = url.pathname;
const slug = extractSlug(req.url);
if (!slug)
return NextResponse.redirect(`${siteConfig.url}/docs/short-urls`, 302);
const isSystemRoute = systemRoutes.some(
(route) => pathname === route || pathname.startsWith(route + "/"),
);
if (isSystemRoute || pathname === "/") {
return NextResponse.next();
}
// 兼容旧版 /s
if (pathname.startsWith("/s/")) {
const slug = extractSlug(req.url);
const newUrl = new URL(`/${slug}`, siteConfig.url);
url.searchParams.forEach((value, key) => {
newUrl.searchParams.set(key, value);
});
return NextResponse.redirect(newUrl.toString(), 302);
}
const slug = pathname.substring(1);
if (!slug || slug.includes("/")) {
return NextResponse.next();
}
const slugRegex = /^[a-zA-Z0-9_-]+$/;
if (!slugRegex.test(slug)) {
return NextResponse.next();
}
return await processShortUrl(req, slug, url);
}
async function processShortUrl(req: NextAuthRequest, slug: string, url: URL) {
const headers = req.headers;
const ip = isVercel ? ipAddress(req) : extractRealIP(headers);
const ua = getUserAgent(req);
const geo = await getGeolocation(req, ip || "::1");
const url = new URL(req.url);
const password = url.searchParams.get("password") || "";
const trackingData = {
@@ -100,7 +150,7 @@ async function handleShortUrl(req: NextAuthRequest) {
}
function extractSlug(url: string): string | null {
const match = url.match(/([^/?]+)(?:\?.*)?$/);
const match = url.match(/\/s\/([^/?]+)(?:\?.*)?$/);
return match ? match[1] : null;
}

View File

@@ -77,46 +77,11 @@ const nextConfig = {
destination: "/docs/developer/installation",
permanent: true,
},
{
source: "/0",
destination: "https://wr.do/s/0",
permanent: true,
},
{
source: "/9",
destination: "https://wr.do/s/9",
permanent: true,
},
{
source: "/ai",
destination: "https://wr.do/s/ai?ref=wrdo",
permanent: true,
},
{
source: "/cps",
destination: "https://wr.do/s/cps",
permanent: true,
},
{
source: "/x",
destination: "https://wr.do/s/x",
permanent: true,
},
{
source: "/solo",
destination: "https://wr.do/s/solo",
permanent: true,
},
{
source: "/rmbg",
destination: "https://wr.do/s/rmbg",
permanent: true,
},
{
source: "/llk",
destination: "https://wr.do/s/llk",
permanent: true,
},
];
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "wr.do",
"version": "1.1.4",
"version": "1.1.5",
"author": {
"name": "oiov",
"url": "https://github.com/oiov"

View File

@@ -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.1.4",
"versionName": "1.1.5",
"versionCode": "1",
"start_url": "/",
"orientation": "portrait",

View File

@@ -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.1.4",
"versionName": "1.1.5",
"versionCode": "1",
"start_url": "/",
"orientation": "portrait",

File diff suppressed because one or more lines are too long