feats: add admin actions and upd docs

This commit is contained in:
oiov
2024-08-04 17:21:58 +08:00
parent dae43fb22e
commit d1ad4a4aee
13 changed files with 289 additions and 35 deletions
+9
View File
@@ -43,6 +43,15 @@ pnpm install
pnpm dev
```
## Legitimacy review
- To avoid abuse, applications without website content will be rejected
- To avoid domain name conflicts, please check before applying
- Completed website construction or released open source project (ready to build website for open source project)
- Political sensitivity, violence, pornography, link jumping, VPN, reverse proxy services, and other illegal or sensitive content must not appear on the website
**Administrators will conduct domain name checks periodically to clean up domain names that violate the above rules, have no content, and are not open source related**
## Community Group
- Discord: https://discord.gg/d68kWCBDEs
@@ -104,8 +104,16 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
) : (
<div className="grid gap-2">
<CardTitle>DNS Records</CardTitle>
<CardDescription className="text-balance">
See{" "}
<CardDescription className="hidden text-balance sm:block">
Before starting, please read the{" "}
<Link
target="_blank"
className="font-semibold text-yellow-600 after:content-['↗'] hover:underline"
href="/docs/dns-records#legitimacy-review"
>
Legitimacy review
</Link>
. See{" "}
<Link
target="_blank"
className="text-blue-500 hover:underline"
@@ -113,7 +121,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
>
examples
</Link>{" "}
about how to use it.
for more.
</CardDescription>
</div>
)}
+60
View File
@@ -0,0 +1,60 @@
import { env } from "@/env.mjs";
import { deleteDNSRecord } from "@/lib/cloudflare";
import { deleteUserRecord } from "@/lib/dto/cloudflare-dns-record";
import { checkUserStatus } from "@/lib/dto/user";
import { getCurrentUser } from "@/lib/session";
export async function POST(req: Request) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
if (user.role !== "ADMIN") {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const { record_id, zone_id, userId, active } = await req.json();
if (!record_id || !userId) {
return Response.json("RecordId and userId are required", {
status: 400,
statusText: "RecordId and userId are required",
});
}
const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) {
return Response.json("API key、zone iD and email are required", {
status: 400,
statusText: "API key、zone iD and email are required",
});
}
// Delete cf dns record first.
const res = await deleteDNSRecord(
CLOUDFLARE_ZONE_ID,
CLOUDFLARE_API_KEY,
CLOUDFLARE_EMAIL,
record_id,
);
if (res && res.result?.id) {
// Then delete user record.
await deleteUserRecord(userId, record_id, zone_id, active);
return Response.json("success", {
status: 200,
statusText: "success",
});
}
return Response.json({
status: 501,
statusText: "Not Implemented",
});
} catch (error) {
console.error(error);
return Response.json(error?.statusText || error, {
status: error.status || 500,
statusText: error.statusText || "Server error",
});
}
}
+76
View File
@@ -0,0 +1,76 @@
import { env } from "@/env.mjs";
import { updateDNSRecord } from "@/lib/cloudflare";
import { updateUserRecord } from "@/lib/dto/cloudflare-dns-record";
import { checkUserStatus } from "@/lib/dto/user";
import { getCurrentUser } from "@/lib/session";
export async function POST(req: Request) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
if (user.role !== "ADMIN") {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) {
return Response.json("API key、zone iD and email are required", {
status: 400,
statusText: "API key、zone iD and email are required",
});
}
const { record, recordId, userId } = await req.json();
if (!recordId || !userId) {
return Response.json("RecordId and userId are required", {
status: 400,
statusText: "RecordId and userId are required",
});
}
const data = await updateDNSRecord(
CLOUDFLARE_ZONE_ID,
CLOUDFLARE_API_KEY,
CLOUDFLARE_EMAIL,
recordId,
record,
);
if (!data.success || !data.result?.id) {
return Response.json(data.errors, {
status: 501,
statusText: `An error occurred. ${data.errors}`,
});
} else {
const res = await updateUserRecord(userId, {
record_id: data.result.id,
zone_id: data.result.zone_id,
zone_name: data.result.zone_name,
name: data.result.name,
type: data.result.type,
content: data.result.content,
proxied: data.result.proxied,
proxiable: data.result.proxiable,
ttl: data.result.ttl,
comment: data.result.comment ?? "",
tags: data.result.tags?.join("") ?? "",
modified_on: data.result.modified_on,
active: 1,
});
if (res.status !== "success") {
return Response.json(res.status, {
status: 502,
statusText: `An error occurred. ${res.status}`,
});
}
return Response.json(res.data);
}
} catch (error) {
return Response.json(error?.statusText || error, {
status: error?.status || 500,
statusText: error?.statusText || "Server error",
});
}
}
+34
View File
@@ -0,0 +1,34 @@
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";
export async function POST(req: Request) {
try {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
if (user.role !== "ADMIN") {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const { url_id, userId } = await req.json();
if (!url_id || !userId) {
return Response.json("url id is required", {
status: 400,
statusText: "url id is required",
});
}
await deleteUserShortUrl(userId, url_id);
return Response.json("success");
} catch (error) {
return Response.json(error?.statusText || error, {
status: error.status || 500,
statusText: error.statusText || "Server error",
});
}
}
+52
View File
@@ -0,0 +1,52 @@
import { env } from "@/env.mjs";
import { getUserRecords } from "@/lib/dto/cloudflare-dns-record";
import { updateUserShortUrl } 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 {
const user = checkUserStatus(await getCurrentUser());
if (user instanceof Response) return user;
if (user.role !== "ADMIN") {
return Response.json("Unauthorized", {
status: 401,
statusText: "Unauthorized",
});
}
const { data, userId } = await req.json();
if (!data?.id || !userId) {
return Response.json(`Url id is required`, {
status: 400,
statusText: `Url id is required`,
});
}
const { target, url, visible, active, id, expiration } =
createUrlSchema.parse(data);
const res = await updateUserShortUrl({
id,
userId,
userName: "",
target,
url,
visible,
active,
expiration,
});
if (res.status !== "success") {
return Response.json(res.status, {
status: 400,
statusText: `An error occurred. ${res.status}`,
});
}
return Response.json(res.data);
} catch (error) {
return Response.json(error?.statusText || error, {
status: error.status || 500,
statusText: error.statusText || "Server error",
});
}
}
+4 -2
View File
@@ -111,6 +111,7 @@ export function RecordForm({
body: JSON.stringify({
recordId: initData?.record_id,
record: data,
userId: initData?.userId,
}),
});
if (!response.ok || response.status !== 200) {
@@ -136,6 +137,7 @@ export function RecordForm({
record_id: initData?.record_id,
zone_id: initData?.zone_id,
active: initData?.active,
userId: initData?.userId,
}),
});
if (!response.ok || response.status !== 200) {
@@ -217,7 +219,7 @@ export function RecordForm({
? "Content"
: currentRecordType === "A"
? "IPv4 address"
: "Not support"
: "Content"
}
>
<div className="flex w-full items-center gap-2">
@@ -242,7 +244,7 @@ export function RecordForm({
? "Required. E.g. www.example.com"
: currentRecordType === "A"
? "Required. E.g. 8.8.8.8"
: "Not support"}
: "Required."}
</p>
)}
</div>
+8 -5
View File
@@ -78,7 +78,7 @@ export function UrlForm({
const handleCreateUrl = async (data: ShortUrlFormData) => {
startTransition(async () => {
const response = await fetch("/api/url/add", {
const response = await fetch(`${action}/add`, {
method: "POST",
body: JSON.stringify({
data,
@@ -100,9 +100,9 @@ export function UrlForm({
const handleUpdateUrl = async (data: ShortUrlFormData) => {
startTransition(async () => {
if (type === "edit") {
const response = await fetch("/api/url/update", {
const response = await fetch(`${action}/update`, {
method: "POST",
body: JSON.stringify({ data }),
body: JSON.stringify({ data, userId: initData?.userId }),
});
if (!response.ok || response.status !== 200) {
toast.error("Update Failed", {
@@ -121,9 +121,12 @@ export function UrlForm({
const handleDeleteUrl = async () => {
if (type === "edit") {
startDeleteTransition(async () => {
const response = await fetch("/api/url/delete", {
const response = await fetch(`${action}/delete`, {
method: "POST",
body: JSON.stringify({ url_id: initData?.id }),
body: JSON.stringify({
url_id: initData?.id,
userId: initData?.userId,
}),
});
if (!response.ok || response.status !== 200) {
toast.error("Delete Failed", {
+28 -3
View File
@@ -3,8 +3,28 @@ title: DNS Records
description: Create and manage your DNS records.
---
<Callout type="warning" twClass="mt-0">
Please do not abuse the free DNS record management service. If any illegal or malicious activities are discovered, the account will be banned. If you need help, please contact us.
## Legitimacy review
<Callout type="danger" twClass="mb-3">
- To avoid abuse, applications without website content will be rejected
- To avoid domain name conflicts, please check before applying
- Completed website construction or released open source project (ready to build website for open source project)
- Political sensitivity, violence, pornography, link jumping, VPN, reverse proxy services, and other illegal or sensitive content must not appear on the website
**Administrators will conduct domain name checks periodically to clean up domain names that violate the above rules, have no content, and are not open source related**
</Callout>
## 合法性审查
<Callout type="danger" twClass="mb-3">
- 为了避免滥用,无网站内容的申请将被拒绝
- 为了避免域名冲突,请在申请前进行检查
- 已经完成网站建设或已经发布开源项目(准备为开源项目搭建网站)
- 不可在网站中出现政治敏感及暴力、色情、链接跳转、VPN、反向代理服务等违法或敏感内容
**管理员会不定期进行域名检查,对违反以上规则、无内容和非开源相关域名进行清理。**
特别提示:如果您的网站内容不符合中国大陆法律法规,您的域名将会被删除,且不会提供备份。
</Callout>
## What is a DNS Record?
@@ -25,4 +45,9 @@ WR.DO provide a **free DNS record** management service that can help you quickly
1. **Navigate to DNS Management Page**: After logging in, you will see a "DNS Management" option in the navigation bar, click [DNS Management](https://wr.do/dashboard/records) to proceed.
2. **Add Record**: Click the "Add Record" button and select the type of record you need (such as CNAME, A, etc.).
3. **Fill in Details**: Enter the hostname, target address, and other details as prompted.
4. **Save Record**: After confirming everything is correct, click the "Save" button.
4. **Save Record**: After confirming everything is correct, click the "Save" button.
## Examples
- [Vercel custom domain](/docs/examples/vercel)
- [Zeabur custom domain](/docs/examples/zeabur)
-4
View File
@@ -3,10 +3,6 @@ title: Introduction
description: Welcome to the WR.DO documentation.
---
<Callout type="danger" twClass="mb-3">
We will rigorously review the legitimacy of DNS records and URLs, and strongly advise users against using WRDO's free services for any illegal activities, such as copyright infringement, dissemination of malicious software, or phishing attempts, among others. These measures are in place to ensure the sustainable and stable operation of WRDO, providing a secure and reliable service environment for all users.
</Callout>
{/* <img className="rounded-xl border md:rounded-lg shadow-md" src="/_static/images/light-preview.png"/> */}
## Features
+1 -11
View File
@@ -33,11 +33,6 @@ export async function createUserRecord(
data: UserRecordFormData,
) {
try {
const session = await auth();
if (!session?.user || session?.user.id !== userId) {
throw new Error("Unauthorized");
}
const {
record_id,
zone_id,
@@ -57,7 +52,7 @@ export async function createUserRecord(
const res = await prisma.userRecord.create({
data: {
userId: session.user.id,
userId,
record_id,
zone_id,
zone_name,
@@ -175,11 +170,6 @@ export async function updateUserRecord(
data: UserRecordFormData,
) {
try {
const session = await auth();
if (!session?.user || session?.user.id !== userId) {
throw new Error("Unauthorized");
}
const {
record_id,
zone_id,
+2 -7
View File
@@ -76,15 +76,10 @@ export async function getUserShortUrlCount(
export async function createUserShortUrl(data: ShortUrlFormData) {
try {
const session = await auth();
if (!session?.user) {
throw new Error("Unauthorized");
}
const res = await prisma.userUrl.create({
data: {
userId: session.user?.id!,
userName: session.user?.name || "Anonymous",
userId: data.userId,
userName: data.userName || "Anonymous",
target: data.target,
url: data.url,
visible: data.visible,
+4
View File
@@ -57,6 +57,10 @@ export const RECORD_TYPE_ENUMS = [
value: "A",
label: "A",
},
{
value: "TXT",
label: "TXT",
},
];
export const TTL_ENUMS = [
{