feat: support create short link with api
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
export default function ApiReference() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>API Reference</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Badge>POST /api/v1/short</Badge>
|
||||
<div className="mt-2">
|
||||
We provide a simple API for creating short URLs. See usage
|
||||
instructions at{" "}
|
||||
<Link
|
||||
href={"/docs/short-urls#api-reference"}
|
||||
target="_blank"
|
||||
className="font-semibold after:content-['_↗'] hover:text-blue-500 hover:underline"
|
||||
>
|
||||
api reference
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
import ApiReference from "./api-reference";
|
||||
import LiveLog from "./live-logs";
|
||||
import UserUrlsList from "./url-list";
|
||||
|
||||
@@ -23,7 +24,7 @@ export default async function DashboardPage() {
|
||||
heading="Manage Short URLs"
|
||||
text="List and manage short urls."
|
||||
link="/docs/short-urls"
|
||||
linkText="Short urls."
|
||||
linkText="short urls."
|
||||
/>
|
||||
<UserUrlsList
|
||||
user={{
|
||||
@@ -35,6 +36,7 @@ export default async function DashboardPage() {
|
||||
action="/api/url"
|
||||
/>
|
||||
<LiveLog admin={false} />
|
||||
<ApiReference />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ export async function POST(req: Request) {
|
||||
const user = checkUserStatus(await getCurrentUser());
|
||||
if (user instanceof Response) return user;
|
||||
|
||||
const { NEXT_PUBLIC_FREE_URL_QUOTA } = env;
|
||||
|
||||
// check quota
|
||||
const user_urls_count = await getUserShortUrlCount(user.id);
|
||||
if (user_urls_count >= Team_Plan_Quota[user.team].SL_NewLinks) {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { checkApiKey } from "@/lib/dto/api-key";
|
||||
import { createUserShortUrl, getUserShortUrlCount } from "@/lib/dto/short-urls";
|
||||
import { Team_Plan_Quota } from "@/lib/team";
|
||||
import { createUrlSchema } from "@/lib/validations/url";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const custom_api_key = req.headers.get("wrdo-api-key");
|
||||
if (!custom_api_key) {
|
||||
return Response.json("Unauthorized", {
|
||||
status: 401,
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the API key is valid
|
||||
const user = await checkApiKey(custom_api_key);
|
||||
if (!user?.id) {
|
||||
return Response.json(
|
||||
"Invalid API key. You can get your API key from https://wr.do/dashboard/settings.",
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
// check quota
|
||||
const user_urls_count = await getUserShortUrlCount(user.id);
|
||||
if (user_urls_count >= Team_Plan_Quota[user.team || "free"].SL_NewLinks) {
|
||||
return Response.json("Your short urls have reached the free limit.", {
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
|
||||
const data = await req.json();
|
||||
|
||||
const { target, url, prefix, visible, active, expiration } =
|
||||
createUrlSchema.parse(data);
|
||||
if (!target || !url) {
|
||||
return Response.json("Target url and slug are required", {
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await createUserShortUrl({
|
||||
userId: user.id,
|
||||
userName: user.name || "Anonymous",
|
||||
target,
|
||||
url,
|
||||
prefix,
|
||||
visible,
|
||||
active,
|
||||
expiration,
|
||||
});
|
||||
if (res.status !== "success") {
|
||||
return Response.json(res.status, {
|
||||
status: 502,
|
||||
});
|
||||
}
|
||||
return Response.json(res.data);
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
});
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -16,11 +16,6 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/quick-start",
|
||||
icon: "page",
|
||||
},
|
||||
{
|
||||
title: "DNS Records",
|
||||
href: "/docs/dns-records",
|
||||
icon: "page",
|
||||
},
|
||||
{
|
||||
title: "Short URLs",
|
||||
href: "/docs/short-urls",
|
||||
@@ -31,6 +26,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: "/docs/emails",
|
||||
icon: "page",
|
||||
},
|
||||
{
|
||||
title: "DNS Records",
|
||||
href: "/docs/dns-records",
|
||||
icon: "page",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -88,15 +88,21 @@ Remember to add your environment variables in `wrangler.jsonc` before deploy.
|
||||
|
||||
### Config your domain email rule
|
||||
|
||||
Via:
|
||||
```bash
|
||||
https://dash.cloudflare.com/[account_id]/[zone_name]/email/routing/routes
|
||||
```
|
||||
|
||||
Via the [https://dash.cloudflare.com/account_id/zone_name/email/routing/routes](https://dash.cloudflare.com/[account_id]/[zone_name]/email/routing/routes]),
|
||||
edit `Catch-all address`, select:
|
||||
- `Action` -> `Send to a worker`
|
||||
- `Destination` -> `wrdo-email-worker`(The worker name you deploy).
|
||||
|
||||
Then save and active it.
|
||||
|
||||
<Callout type="warning" twClass="mb-3">
|
||||
Once you add a new domain, you need to perform the same action, the email worker can be the same.
|
||||
</Callout>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,63 @@ WR.DO provides a simple access statistics feature that can be used to track the
|
||||
|
||||
Once the generated short chain becomes invalid, it will not be deleted. When accessing the short chain again, it will be redirected to this page. Users can reset the short chain validity period to activate.
|
||||
|
||||
## API Reference
|
||||
|
||||
The `POST /api/v1/short` endpoint allows you to create a new short link for a given long URL.
|
||||
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "wrdo-api-key: YOUR_API_KEY" \
|
||||
-d '{
|
||||
"target": "https://www.oiov.dev",
|
||||
"url": "abc123",
|
||||
"expiration": "-1",
|
||||
"prefix": "wr.do",
|
||||
"visible": 1,
|
||||
"active": 1
|
||||
}' \
|
||||
https://wr.do/api/v1/short
|
||||
```
|
||||
|
||||
### Request Body (Params)
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "https://www.oiov.dev", // required
|
||||
"url": "abc123", // required, slug
|
||||
"expiration": "-1", // optional, seconds, default: "-1", "-1" means no expiration; "60" means 60 seconds
|
||||
"prefix": "wr.do", // optional, default: wr.do
|
||||
"visible": 1, // optional, default: 1, 1: visible, 0: invisible
|
||||
"active": 1 // optional, default: 1, 1: active, 0: inactive
|
||||
}
|
||||
```
|
||||
|
||||
### Authorization Header
|
||||
|
||||
- `wrdo-api-key`: You can use your API key to authenticate your requests.
|
||||
You can find your API key in your [account settings](/dashboard/settings).
|
||||
Add the header `wrdo-api-key: YOUR_API_KEY` to your request.
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "c_abcd123",
|
||||
"userId": "string",
|
||||
"userName": "string",
|
||||
"target": "https://www.example.com",
|
||||
"url": "abc123",
|
||||
"prefix": "wr.do",
|
||||
"visible": 1,
|
||||
"active": 1,
|
||||
"expiration": "-1",
|
||||
"createdAt": 2025-01-01T00:00:00.000Z,
|
||||
"updatedAt": 2025-01-01T00:00:00.000Z
|
||||
}
|
||||
```
|
||||
|
||||
## Problems
|
||||
|
||||
### Expired Links
|
||||
|
||||
+3
-3
@@ -11,15 +11,15 @@ export const getApiKeyByUserId = async (userId: string) => {
|
||||
|
||||
export const checkApiKey = async (apiKey: string) => {
|
||||
return prisma.user.findFirst({
|
||||
where: { apiKey },
|
||||
select: { id: true },
|
||||
where: { apiKey, active: 1 },
|
||||
select: { id: true, team: true, name: true },
|
||||
});
|
||||
};
|
||||
|
||||
export const generateApiKey = async (userId: string) => {
|
||||
const apiKey = crypto.randomUUID();
|
||||
return prisma.user.update({
|
||||
where: { id: userId },
|
||||
where: { id: userId, active: 1 },
|
||||
data: { apiKey },
|
||||
select: { apiKey: true },
|
||||
});
|
||||
|
||||
+101
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user