Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc4c6c5e96 | ||
|
|
3301570213 | ||
|
|
c65176e607 | ||
|
|
55aa93d117 | ||
|
|
7c61b7fc44 | ||
|
|
bc7f86119c | ||
|
|
bc1490f0fd | ||
|
|
7bf2aa8b3c | ||
|
|
ba086b602f | ||
|
|
0d793ee31c | ||
|
|
7579be007f | ||
|
|
515e7d2719 | ||
|
|
c9cfdfc07a | ||
|
|
c589afd859 | ||
|
|
f10f8af0f6 | ||
|
|
cbeba449ef | ||
|
|
fa02ca000b | ||
|
|
af01d60d9b | ||
|
|
06f06a8a52 | ||
|
|
fc54d9e176 | ||
|
|
8fab48f849 | ||
|
|
69878126f6 | ||
|
|
0185520445 | ||
|
|
00cb224e84 | ||
|
|
c5a932b9f1 | ||
|
|
becc328811 | ||
|
|
c2ae4c78f7 | ||
|
|
40f2483332 | ||
|
|
a27eb84d61 | ||
|
|
01b80eaf9e | ||
|
|
1e713ea613 | ||
|
|
1eb7c71ff9 | ||
|
|
b9bf2733f9 | ||
|
|
1e48c209f7 | ||
|
|
fff455312e | ||
|
|
a5626ebefe | ||
|
|
400b1aac8d | ||
|
|
872baa7933 | ||
|
|
04b47b62ad | ||
|
|
8894d2daae | ||
|
|
3145ef884d |
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
# .dockerignore
|
||||
# node_modules
|
||||
# npm-debug.log
|
||||
# README.md
|
||||
# .env*
|
||||
# .next
|
||||
# .git
|
||||
10
.env.example
10
.env.example
@@ -4,9 +4,10 @@
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Authentication (NextAuth.js)
|
||||
# Authentication (NextAuth.js 5.0.x)
|
||||
# -----------------------------------------------------------------------------
|
||||
AUTH_SECRET=
|
||||
AUTH_SECRET=abc123
|
||||
AUTH_URL=http://localhost:3000
|
||||
|
||||
GOOGLE_CLIENT_ID=
|
||||
GOOGLE_CLIENT_SECRET=
|
||||
@@ -37,5 +38,6 @@ SCREENSHOTONE_BASE_URL=https://shot.wr.do
|
||||
# GitHub api token for getting gitHub stars count
|
||||
GITHUB_TOKEN=
|
||||
|
||||
|
||||
|
||||
# Skip DB check and migration (only for docker), default is true. if false, will check and migrate database each time start docker compose.
|
||||
SKIP_DB_CHECK=true
|
||||
SKIP_DB_MIGRATION=true
|
||||
64
.github/workflows/docker-build-push.yml
vendored
Normal file
64
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Build and Push Docker Image to GHCR
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}/wrdo
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
# 检出代码
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# 设置 Docker Buildx(支持多平台构建)
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# 登录到 GitHub Container Registry
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# 提取 Docker 镜像元数据(标签、版本等)
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha,format=short
|
||||
type=ref,event=branch,prefix=
|
||||
type=ref,event=tag
|
||||
|
||||
# 构建并推送 Docker 镜像
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true # ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
ENVIRONMENT=${{ github.event.inputs.environment || 'production' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
60
Dockerfile
Normal file
60
Dockerfile
Normal file
@@ -0,0 +1,60 @@
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
FROM base AS deps
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pnpm config set registry https://registry.npmmirror.com
|
||||
|
||||
RUN pnpm i --frozen-lockfile
|
||||
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
FROM base AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV IS_DOCKER=true
|
||||
|
||||
RUN pnpm add npm-run-all dotenv prisma@5.17.0 @prisma/client@5.17.0
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
# Check db
|
||||
COPY scripts/check-db.js /app/scripts/check-db.js
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV PORT=3000
|
||||
|
||||
# CMD ["node", "server.js"]
|
||||
CMD ["pnpm", "start-docker"]
|
||||
46
README-zh.md
46
README-zh.md
@@ -14,24 +14,27 @@
|
||||
- <20>😀 **权限管理**:方便审核的管理员面板
|
||||
- 🔒 **安全可靠**:基于 Cloudflare 强大的 DNS API
|
||||
|
||||
## Screenshots
|
||||
## 截图预览
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/light-preview.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/example_02.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/example_01.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/realtime-globe.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/example_03.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/domains.png" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 快速开始
|
||||
|
||||
查看开发者[快速开始](https://wr.do/docs/developer/quick-start)的详细文档。
|
||||
|
||||
查看有关[快速开始](https://wr.do/docs/quick-start)的文档。
|
||||
|
||||
## 自托管教程
|
||||
|
||||
### 要求
|
||||
|
||||
- [Vercel](https://vercel.com) 账户用于部署应用
|
||||
@@ -43,6 +46,23 @@
|
||||
|
||||
查看 [email worker](https://wr.do/docs/developer/cloudflare-email-worker) 文档用于邮件接收。
|
||||
|
||||
## 自部署教程
|
||||
|
||||
### 使用 Vercel 部署
|
||||
|
||||
[](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=NEXT_PUBLIC_OPEN_SIGNUP&env=GITHUB_TOKEN)
|
||||
|
||||
记得填写必要的环境变量。
|
||||
|
||||
### 使用 Docker Compose 部署
|
||||
|
||||
|
||||
在服务器中创建一个文件夹,进入该文件夹并新建`docker-compose.yml`文件,填写必要的环境变量,然后执行:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 本地开发
|
||||
|
||||
将 `.env.example` 复制为 `.env` 并填写必要的环境变量。
|
||||
@@ -63,7 +83,7 @@ pnpm postinstall
|
||||
pnpm db:push
|
||||
```
|
||||
|
||||
#### 激活管理员面板
|
||||
#### 管理员初始化
|
||||
|
||||
Follow https://localhost:3000/setup
|
||||
|
||||
|
||||
65
README.md
65
README.md
@@ -17,20 +17,24 @@
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/light-preview.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/example_02.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/example_01.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/realtime-globe.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/example_03.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/domains.png" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Quick Start
|
||||
|
||||
See usage docs about [guide](https://wr.do/docs/quick-start) for quick start.
|
||||
|
||||
## Self-hosted Tutorial
|
||||
|
||||
See step by step installation tutorial at [Quick Start for Developer](https://wr.do/docs/developer/quick-start).
|
||||
|
||||
### Requirements
|
||||
@@ -44,15 +48,41 @@ See more docs about [developer](https://wr.do/docs/developer/installation).
|
||||
|
||||
See docs about [email worker](https://wr.do/docs/developer/cloudflare-email-worker).
|
||||
|
||||
## Local development
|
||||
## Self-hosted
|
||||
|
||||
copy `.env.example` to `.env` and fill in the necessary environment variables.
|
||||
### Deploy with Vercel
|
||||
|
||||
[](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=NEXT_PUBLIC_OPEN_SIGNUP&env=GITHUB_TOKEN)
|
||||
|
||||
Remember to fill in the necessary environment variables.
|
||||
|
||||
### Deploy with Docker Compose
|
||||
|
||||
Create a new folder and copy the `docker-compose.yml`、`.env` file to the folder.
|
||||
|
||||
```yml
|
||||
- wrdo
|
||||
| - docker-compose.yml
|
||||
| - .env
|
||||
```
|
||||
|
||||
Fill in the environment variables in the `.env` file, then:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
git clone https://github.com/oiov/wr.do
|
||||
cd wr.do
|
||||
pnpm install
|
||||
```
|
||||
|
||||
copy `.env.example` to `.env` and fill in the necessary environment variables.
|
||||
|
||||
```bash
|
||||
# run on localhost:3000
|
||||
pnpm dev
|
||||
```
|
||||
@@ -68,21 +98,12 @@ pnpm db:push
|
||||
|
||||
Follow https://localhost:3000/setup
|
||||
|
||||
## 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/AHPQYuZu3m
|
||||
- 微信群:
|
||||
|
||||

|
||||
<img width="300" src="https://wr.do/s/group" />
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function LoginPage() {
|
||||
>
|
||||
<>
|
||||
<Icons.chevronLeft className="mr-2 size-4" />
|
||||
Back
|
||||
Back {siteConfig.openSignup ? "Home" : ""}
|
||||
</>
|
||||
</Link>
|
||||
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
|
||||
@@ -140,12 +140,12 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
{action.includes("/admin") ? (
|
||||
<CardDescription className="text-balance text-lg font-bold">
|
||||
<span>Total Records:</span>{" "}
|
||||
<span>Total Subdomains:</span>{" "}
|
||||
<span className="font-bold">{data && data.total}</span>
|
||||
</CardDescription>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
<CardTitle>DNS Records</CardTitle>
|
||||
<CardTitle>Subdomains</CardTitle>
|
||||
<CardDescription className="hidden text-balance sm:block">
|
||||
Please read the{" "}
|
||||
<Link
|
||||
|
||||
@@ -4,6 +4,8 @@ import { FeatureMap, getDomainsByFeatureClient } from "@/lib/dto/domains";
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Get domains by feature for frontend
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { ipAddress } from "@vercel/functions";
|
||||
|
||||
import { getIpInfo } from "@/lib/utils";
|
||||
import { extractRealIP, getIpInfo } from "@/lib/geo";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const data = getIpInfo(req);
|
||||
const ip = ipAddress(req);
|
||||
const data = await getIpInfo(req);
|
||||
|
||||
return Response.json({
|
||||
ip,
|
||||
ip: data.ip,
|
||||
city: data.city,
|
||||
region: data.region,
|
||||
country: data.country,
|
||||
|
||||
@@ -3,7 +3,8 @@ import TurndownService from "turndown";
|
||||
|
||||
import { checkApiKey } from "@/lib/dto/api-key";
|
||||
import { createScrapeMeta } from "@/lib/dto/scrape";
|
||||
import { getIpInfo, isLink } from "@/lib/utils";
|
||||
import { getIpInfo } from "@/lib/geo";
|
||||
import { isLink } from "@/lib/utils";
|
||||
|
||||
export const revalidate = 600;
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -70,9 +71,9 @@ export async function GET(req: Request) {
|
||||
|
||||
const markdown = turndownService.turndown(mainContent || "");
|
||||
|
||||
const stats = getIpInfo(req);
|
||||
const stats = await getIpInfo(req);
|
||||
await createScrapeMeta({
|
||||
ip: stats.ip,
|
||||
ip: stats.ip || "::1",
|
||||
type: "markdown",
|
||||
referer: stats.referer,
|
||||
city: stats.city,
|
||||
|
||||
@@ -2,7 +2,8 @@ import cheerio from "cheerio";
|
||||
|
||||
import { checkApiKey } from "@/lib/dto/api-key";
|
||||
import { createScrapeMeta } from "@/lib/dto/scrape";
|
||||
import { getIpInfo, isLink, removeUrlSuffix } from "@/lib/utils";
|
||||
import { getIpInfo } from "@/lib/geo";
|
||||
import { isLink, removeUrlSuffix } from "@/lib/utils";
|
||||
|
||||
export const revalidate = 600;
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -84,9 +85,9 @@ export async function GET(req: Request) {
|
||||
$("meta[name='author']").attr("content") ||
|
||||
$("meta[property='author']").attr("content");
|
||||
|
||||
const stats = getIpInfo(req);
|
||||
const stats = await getIpInfo(req);
|
||||
await createScrapeMeta({
|
||||
ip: stats.ip,
|
||||
ip: stats.ip || "::1",
|
||||
type: "meta-info",
|
||||
referer: stats.referer,
|
||||
city: stats.city,
|
||||
|
||||
@@ -2,9 +2,10 @@ import { ImageResponse } from "@vercel/og";
|
||||
|
||||
import { checkApiKey } from "@/lib/dto/api-key";
|
||||
import { createScrapeMeta } from "@/lib/dto/scrape";
|
||||
import { getIpInfo } from "@/lib/geo";
|
||||
import { WRDO_QR_LOGO } from "@/lib/qr/constants";
|
||||
import { QRCodeSVG } from "@/lib/qr/utils";
|
||||
import { getIpInfo, getSearchParams } from "@/lib/utils";
|
||||
import { getSearchParams } from "@/lib/utils";
|
||||
import { getQRCodeQuerySchema } from "@/lib/validations/qr";
|
||||
|
||||
const CORS_HEADERS = {
|
||||
@@ -41,9 +42,9 @@ export async function GET(req: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
const stats = getIpInfo(req);
|
||||
const stats = await getIpInfo(req);
|
||||
await createScrapeMeta({
|
||||
ip: stats.ip,
|
||||
ip: stats.ip || "::1",
|
||||
type: "qrcode",
|
||||
referer: stats.referer,
|
||||
city: stats.city,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { checkApiKey } from "@/lib/dto/api-key";
|
||||
import { createScrapeMeta } from "@/lib/dto/scrape";
|
||||
import { getIpInfo, isLink } from "@/lib/utils";
|
||||
import { getIpInfo } from "@/lib/geo";
|
||||
import { isLink } from "@/lib/utils";
|
||||
|
||||
export const revalidate = 60;
|
||||
|
||||
@@ -69,9 +70,9 @@ export async function GET(req: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
const stats = getIpInfo(req);
|
||||
const stats = await getIpInfo(req);
|
||||
await createScrapeMeta({
|
||||
ip: stats.ip,
|
||||
ip: stats.ip || "::1",
|
||||
type: "screenshot",
|
||||
referer: stats.referer,
|
||||
city: stats.city,
|
||||
|
||||
@@ -2,7 +2,8 @@ import cheerio from "cheerio";
|
||||
|
||||
import { checkApiKey } from "@/lib/dto/api-key";
|
||||
import { createScrapeMeta } from "@/lib/dto/scrape";
|
||||
import { getIpInfo, isLink } from "@/lib/utils";
|
||||
import { getIpInfo } from "@/lib/geo";
|
||||
import { isLink } from "@/lib/utils";
|
||||
|
||||
export const revalidate = 600;
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -63,9 +64,9 @@ export async function GET(req: Request) {
|
||||
$("style").remove();
|
||||
const text = $("body").text().trim();
|
||||
|
||||
const stats = getIpInfo(req);
|
||||
const stats = await getIpInfo(req);
|
||||
await createScrapeMeta({
|
||||
ip: stats.ip,
|
||||
ip: stats.ip || "::1",
|
||||
type: "text",
|
||||
referer: stats.referer,
|
||||
city: stats.city,
|
||||
|
||||
1
auth.ts
1
auth.ts
@@ -22,6 +22,7 @@ export const {
|
||||
handlers: { GET, POST },
|
||||
auth,
|
||||
} = NextAuth({
|
||||
trustHost: true, // TODO: Test with docker
|
||||
adapter: PrismaAdapter(prisma),
|
||||
session: { strategy: "jwt" },
|
||||
pages: {
|
||||
|
||||
@@ -10,27 +10,19 @@ interface GitHubResponse {
|
||||
}
|
||||
|
||||
async function getGitHubStars(owner: string, repo: string) {
|
||||
const githubToken = process.env.GITHUB_TOKEN;
|
||||
|
||||
if (!githubToken) {
|
||||
throw new Error("GitHub token is not configured");
|
||||
}
|
||||
|
||||
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
Authorization: `token ${githubToken}`,
|
||||
"User-Agent": "NextJS-App",
|
||||
const res = await fetch(
|
||||
`https://wr.do/api/github?owner=${owner}&repo=${repo}`,
|
||||
{
|
||||
next: { revalidate: 3600 },
|
||||
},
|
||||
next: { revalidate: 3600 },
|
||||
});
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch GitHub stars");
|
||||
}
|
||||
|
||||
const data: GitHubResponse = await res.json();
|
||||
return data.stargazers_count;
|
||||
const data = await res.json();
|
||||
return data.stars;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SidebarNavItem, SiteConfig } from "types";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
const site_url = env.NEXT_PUBLIC_APP_URL;
|
||||
const site_url = env.NEXT_PUBLIC_APP_URL || "http://localhost:3030";
|
||||
const open_signup = env.NEXT_PUBLIC_OPEN_SIGNUP;
|
||||
const email_r2_domain = env.NEXT_PUBLIC_EMAIL_R2_DOMAIN || "";
|
||||
|
||||
|
||||
48
content/docs/developer/deploy.mdx
Normal file
48
content/docs/developer/deploy.mdx
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Deploy Guide
|
||||
description: Choose your deployment method
|
||||
---
|
||||
|
||||
## Deploy with Vercel (Recommended)
|
||||
|
||||
[](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=NEXT_PUBLIC_OPEN_SIGNUP&env=GITHUB_TOKEN)
|
||||
|
||||
Remember to fill in the necessary environment variables.
|
||||
|
||||
## Deploy with Docker Compose
|
||||
|
||||
<Callout type="warning" twClass="mt-4">
|
||||
Please create your database instance before deployment.
|
||||
|
||||
Set `SKIP_DB_CHECK` and `SKIP_DB_MIGRATION` to `false` in the `.env` file, this will start the database check and migration.
|
||||
</Callout>
|
||||
|
||||
Create a new folder and copy the `docker-compose.yml`、`.env` file to the folder.
|
||||
|
||||
```bash
|
||||
- wrdo
|
||||
| - docker-compose.yml
|
||||
| - .env
|
||||
```
|
||||
|
||||
Fill in the environment variables in the `.env` file, then:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Deploy with Docker Compose (Local DB)
|
||||
|
||||
Create a new folder and copy the `docker-compose-localdb.yml`、`.env` file to the folder.
|
||||
|
||||
```bash
|
||||
- wrdo
|
||||
| - docker-compose.yml
|
||||
| - .env
|
||||
```
|
||||
|
||||
Fill in the environment variables in the `.env` file, then:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -205,6 +205,10 @@ Follow the steps below:
|
||||
<strong>You must add at least one domain to start using short links, email or subdomain management features.</strong>
|
||||
</Callout>
|
||||
|
||||
## 9. Deploy
|
||||
|
||||
See [Deploy Guide](/docs/developer/deploy).
|
||||
|
||||
## Q & A
|
||||
|
||||
### Worker Error - Too many redirects
|
||||
|
||||
@@ -15,13 +15,20 @@ description: Welcome to the WR.DO documentation.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<img className="rounded-xl border mt-4 md:rounded-lg shadow-md" src="/_static/images/light-preview.png"/>
|
||||
|
||||
<img className="rounded-xl border mt-4 md:rounded-lg shadow-md" src="/_static/images/example_02.png"/>
|
||||
|
||||
<img className="rounded-xl border mt-4 md:rounded-lg shadow-md" src="/_static/images/example_01.png"/>
|
||||
|
||||
<img className="rounded-xl border mt-4 md:rounded-lg shadow-md" src="/_static/images/example_03.png"/>
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/light-preview.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/example_02.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/example_01.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/realtime-globe.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://wr.do/_static/images/example_03.png" /></td>
|
||||
<td><img src="https://wr.do/_static/images/domains.png" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
53
docker-compose-localdb.yml
Normal file
53
docker-compose-localdb.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
services:
|
||||
app:
|
||||
image: ghcr.io/oiov/wr.do/wrdo:${TAG:-latest}
|
||||
container_name: wrdo
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DATABASE_URL: postgres://postgres:postgres@postgres:5432/wrdo
|
||||
AUTH_SECRET: ${AUTH_SECRET:-your-auth-secret}
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
||||
NEXT_PUBLIC_OPEN_SIGNUP: ${NEXT_PUBLIC_OPEN_SIGNUP}
|
||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
|
||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
|
||||
GITHUB_ID: ${GITHUB_ID}
|
||||
GITHUB_SECRET: ${GITHUB_SECRET}
|
||||
LinuxDo_CLIENT_ID: ${LinuxDo_CLIENT_ID}
|
||||
LinuxDo_CLIENT_SECRET: ${LinuxDo_CLIENT_SECRET}
|
||||
RESEND_API_KEY: ${RESEND_API_KEY}
|
||||
NEXT_PUBLIC_EMAIL_R2_DOMAIN: ${NEXT_PUBLIC_EMAIL_R2_DOMAIN}
|
||||
NEXT_PUBLIC_GOOGLE_ID: ${NEXT_PUBLIC_GOOGLE_ID}
|
||||
SCREENSHOTONE_BASE_URL: ${SCREENSHOTONE_BASE_URL}
|
||||
GITHUB_TOKEN: ${GITHUB_TOKEN}
|
||||
SKIP_DB_CHECK: ${SKIP_DB_CHECK}
|
||||
SKIP_DB_MIGRATION: ${SKIP_DB_MIGRATION}
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- wrdo-network
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=wrdo
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
networks:
|
||||
- wrdo-network
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
name: wrdo-postgres-data
|
||||
|
||||
networks:
|
||||
wrdo-network:
|
||||
driver: bridge
|
||||
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
app:
|
||||
image: ghcr.io/oiov/wr.do/wrdo:${TAG:-latest}
|
||||
container_name: wrdo
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
AUTH_SECRET: ${AUTH_SECRET:-your-auth-secret}
|
||||
AUTH_URL: ${AUTH_URL}
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
|
||||
NEXT_PUBLIC_OPEN_SIGNUP: ${NEXT_PUBLIC_OPEN_SIGNUP}
|
||||
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
|
||||
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
|
||||
GITHUB_ID: ${GITHUB_ID}
|
||||
GITHUB_SECRET: ${GITHUB_SECRET}
|
||||
LinuxDo_CLIENT_ID: ${LinuxDo_CLIENT_ID}
|
||||
LinuxDo_CLIENT_SECRET: ${LinuxDo_CLIENT_SECRET}
|
||||
RESEND_API_KEY: ${RESEND_API_KEY}
|
||||
NEXT_PUBLIC_EMAIL_R2_DOMAIN: ${NEXT_PUBLIC_EMAIL_R2_DOMAIN}
|
||||
NEXT_PUBLIC_GOOGLE_ID: ${NEXT_PUBLIC_GOOGLE_ID}
|
||||
SCREENSHOTONE_BASE_URL: ${SCREENSHOTONE_BASE_URL}
|
||||
GITHUB_TOKEN: ${GITHUB_TOKEN}
|
||||
SKIP_DB_CHECK: ${SKIP_DB_CHECK}
|
||||
SKIP_DB_MIGRATION: ${SKIP_DB_MIGRATION}
|
||||
networks:
|
||||
- wrdo-network
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
wrdo-network:
|
||||
driver: bridge
|
||||
8
env.mjs
8
env.mjs
@@ -6,22 +6,22 @@ export const env = createEnv({
|
||||
// This is optional because it's only used in development.
|
||||
// See https://next-auth.js.org/deployment.
|
||||
NEXTAUTH_URL: z.string().url().optional(),
|
||||
AUTH_SECRET: z.string().min(1),
|
||||
AUTH_SECRET: z.string().optional(),
|
||||
GOOGLE_CLIENT_ID: z.string().optional(),
|
||||
GOOGLE_CLIENT_SECRET: z.string().optional(),
|
||||
GITHUB_ID: z.string().optional(),
|
||||
GITHUB_SECRET: z.string().optional(),
|
||||
LinuxDo_CLIENT_ID: z.string().optional(),
|
||||
LinuxDo_CLIENT_SECRET: z.string().optional(),
|
||||
DATABASE_URL: z.string().min(1),
|
||||
DATABASE_URL: z.string().optional(),
|
||||
RESEND_API_KEY: z.string().optional(),
|
||||
SCREENSHOTONE_BASE_URL: z.string().optional(),
|
||||
GITHUB_TOKEN: z.string().optional(),
|
||||
},
|
||||
client: {
|
||||
NEXT_PUBLIC_APP_URL: z.string().min(1),
|
||||
NEXT_PUBLIC_APP_URL: z.string().optional(),
|
||||
NEXT_PUBLIC_OPEN_SIGNUP: z.string().min(1).default("1"),
|
||||
NEXT_PUBLIC_EMAIL_R2_DOMAIN: z.string().min(1),
|
||||
NEXT_PUBLIC_EMAIL_R2_DOMAIN: z.string().optional(),
|
||||
},
|
||||
runtimeEnv: {
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Resend } from "resend";
|
||||
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
export const resend = new Resend(env.RESEND_API_KEY);
|
||||
export const resend = new Resend(env.RESEND_API_KEY || "re_key");
|
||||
|
||||
export function getVerificationEmailHtml({
|
||||
url,
|
||||
|
||||
150
lib/geo.ts
Normal file
150
lib/geo.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { userAgent } from "next/server";
|
||||
import { Geo, geolocation, ipAddress } from "@vercel/functions";
|
||||
import { NextAuthRequest } from "next-auth/lib";
|
||||
import UAParser from "ua-parser-js";
|
||||
|
||||
interface GeoLocation extends Geo {
|
||||
ip?: string;
|
||||
}
|
||||
|
||||
const isVercel = process.env.VERCEL;
|
||||
|
||||
export async function getGeolocation(
|
||||
req: NextAuthRequest,
|
||||
ip: string,
|
||||
): Promise<GeoLocation | null> {
|
||||
// console.log("[Runtime Env]", isVercel ? "Vercel" : "Other");
|
||||
|
||||
if (isVercel) {
|
||||
return geolocation(req);
|
||||
} else {
|
||||
return await getClientGeolocationWithIpApi(ip);
|
||||
}
|
||||
}
|
||||
|
||||
export function getUserAgent(req: NextAuthRequest) {
|
||||
if (isVercel) {
|
||||
return userAgent(req);
|
||||
} else {
|
||||
const headers = req.headers;
|
||||
const userAgent = headers.get("user-agent") || "";
|
||||
const parser = new UAParser(userAgent);
|
||||
return {
|
||||
browser: parser.getBrowser(),
|
||||
device: parser.getDevice(),
|
||||
os: parser.getOS(),
|
||||
engine: parser.getEngine(),
|
||||
cpu: parser.getCPU(),
|
||||
isBot: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getClientGeolocation(
|
||||
req,
|
||||
ip,
|
||||
): Promise<GeoLocation | null> {
|
||||
// const new_headers = new Headers();
|
||||
// new_headers.set("X-Forwarded-For", ip);
|
||||
// new_headers.set("User-Agent", req.headers.get("user-agent") || "");
|
||||
const response = await fetch(`https://ip.wr.do/api?ip=${ip}`);
|
||||
if (!response.ok) return null;
|
||||
return await response.json();
|
||||
}
|
||||
export async function getClientGeolocationWithIpApi(ip: string) {
|
||||
const response = await fetch(`http://ip-api.com/json/${ip}`);
|
||||
if (!response.ok) return null;
|
||||
const res = await response.json();
|
||||
// {
|
||||
// "query": "154.64.226.29",
|
||||
// "status": "success",
|
||||
// "continent": "North America",
|
||||
// "continentCode": "NA",
|
||||
// "country": "United States",
|
||||
// "countryCode": "US",
|
||||
// "region": "CA",
|
||||
// "regionName": "California",
|
||||
// "city": "Los Angeles",
|
||||
// "district": "",
|
||||
// "zip": "90009",
|
||||
// "lat": 34.0549,
|
||||
// "lon": -118.243,
|
||||
// "timezone": "America/Los_Angeles",
|
||||
// "offset": -25200,
|
||||
// "currency": "USD",
|
||||
// "isp": "Cogent Communications",
|
||||
// "org": "NetLab Global",
|
||||
// "as": "AS51847 Nearoute Limited",
|
||||
// "asname": "NEAROUTE",
|
||||
// "mobile": true,
|
||||
// "proxy": true,
|
||||
// "hosting": false
|
||||
// }
|
||||
return {
|
||||
ip: res.query,
|
||||
country: res.countryCode,
|
||||
countryName: res.country,
|
||||
region: res.region,
|
||||
regionName: res.regionName,
|
||||
city: res.city,
|
||||
latitude: res.lat.toString(),
|
||||
longitude: res.lon.toString(),
|
||||
timezone: res.timezone,
|
||||
};
|
||||
}
|
||||
|
||||
export function extractRealIP(headers: Headers): string {
|
||||
const ipHeaders = [
|
||||
"X-Forwarded-For",
|
||||
"X-Real-IP",
|
||||
"CF-Connecting-IP",
|
||||
"X-Client-IP",
|
||||
"X-Cluster-Client-IP",
|
||||
];
|
||||
|
||||
for (const header of ipHeaders) {
|
||||
const value = headers.get(header);
|
||||
if (value) {
|
||||
const ip = value.split(",")[0].trim();
|
||||
if (isValidIP(ip)) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "::1";
|
||||
}
|
||||
|
||||
function isValidIP(ip: string): boolean {
|
||||
// IPv4 正则
|
||||
const ipv4Regex =
|
||||
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
// IPv6 正则(简化版)
|
||||
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
||||
|
||||
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
||||
}
|
||||
|
||||
export async function getIpInfo(req) {
|
||||
const headers = req.headers;
|
||||
const ip = isVercel ? ipAddress(req) : extractRealIP(headers);
|
||||
const ua = getUserAgent(req);
|
||||
const geo = await getGeolocation(req, ip || "::1");
|
||||
|
||||
const userLanguage =
|
||||
req.headers.get("accept-language")?.split(",")[0] || "en-US";
|
||||
|
||||
return {
|
||||
referer: headers.get("referer") || "(None)",
|
||||
ip: isVercel ? ip : geo?.ip,
|
||||
city: geo?.city || "",
|
||||
region: geo?.region || "",
|
||||
country: geo?.country || "",
|
||||
latitude: geo?.latitude || "",
|
||||
longitude: geo?.longitude || "",
|
||||
flag: geo?.flag,
|
||||
lang: userLanguage,
|
||||
device: ua.device.model || "Unknown",
|
||||
browser: ua.browser.name || "Unknown",
|
||||
};
|
||||
}
|
||||
36
lib/utils.ts
36
lib/utils.ts
@@ -1,12 +1,9 @@
|
||||
import crypto from "crypto";
|
||||
import { Metadata } from "next";
|
||||
import { geolocation } from "@vercel/functions";
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import ms from "ms";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import UAParser from "ua-parser-js";
|
||||
|
||||
import { env } from "@/env.mjs";
|
||||
import { siteConfig } from "@/config/site";
|
||||
|
||||
import { TIME_RANGES } from "./enums";
|
||||
@@ -94,16 +91,12 @@ export function formatTime(input: string | number): string {
|
||||
const locale = navigator.language || "en-US";
|
||||
|
||||
return date.toLocaleTimeString(locale, {
|
||||
second: "numeric",
|
||||
// second: "numeric",
|
||||
minute: "numeric",
|
||||
hour: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export function absoluteUrl(path: string) {
|
||||
return `${env.NEXT_PUBLIC_APP_URL}${path}`;
|
||||
}
|
||||
|
||||
// Utils from precedent.dev
|
||||
export const timeAgo = (timestamp: Date, timeOnly?: boolean): string => {
|
||||
if (!timestamp) return "never";
|
||||
@@ -252,33 +245,6 @@ export function removeUrlSuffix(url: string): string {
|
||||
return url.startsWith("http") ? url.split("//")[1] : url;
|
||||
}
|
||||
|
||||
export function getIpInfo(req: Request) {
|
||||
const geo = geolocation(req);
|
||||
const ua = req.headers.get("user-agent") || "";
|
||||
const parser = new UAParser();
|
||||
parser.setUA(ua);
|
||||
const browser = parser.getBrowser();
|
||||
const device = parser.getDevice();
|
||||
const referer = req.headers.get("referer") || "(None)";
|
||||
const ip = req.headers.get("X-Forwarded-For") || "127.0.0.1";
|
||||
const userLanguage =
|
||||
req.headers.get("accept-language")?.split(",")[0] || "en-US";
|
||||
|
||||
return {
|
||||
referer,
|
||||
ip,
|
||||
city: geo?.city || "",
|
||||
region: geo?.region || "",
|
||||
country: geo?.country || "",
|
||||
latitude: geo?.latitude || "",
|
||||
longitude: geo?.longitude || "",
|
||||
flag: geo?.flag,
|
||||
lang: userLanguage,
|
||||
device: device.model || "Unknown",
|
||||
browser: browser.name || "Unknown",
|
||||
};
|
||||
}
|
||||
|
||||
export function toCamelCase(str: string) {
|
||||
return str
|
||||
.split("-")
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { geolocation } from "@vercel/functions";
|
||||
import { ipAddress } from "@vercel/functions";
|
||||
import { auth } from "auth";
|
||||
import { NextAuthRequest } from "next-auth/lib";
|
||||
import UAParser from "ua-parser-js";
|
||||
|
||||
import { siteConfig } from "./config/site";
|
||||
import { extractRealIP, getGeolocation, getUserAgent } from "./lib/geo";
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||
};
|
||||
|
||||
const isVercel = process.env.VERCEL;
|
||||
|
||||
const redirectMap = {
|
||||
"Missing[0000]": "/docs/short-urls#missing-links",
|
||||
"Expired[0001]": "/docs/short-urls#expired-links",
|
||||
@@ -19,7 +21,6 @@ const redirectMap = {
|
||||
"IncorrectPassword[0005]": "/password-prompt?error=1&slug=",
|
||||
};
|
||||
|
||||
// 提取短链接处理逻辑
|
||||
async function handleShortUrl(req: NextAuthRequest) {
|
||||
if (!req.url.includes("/s/")) return NextResponse.next();
|
||||
|
||||
@@ -27,9 +28,11 @@ async function handleShortUrl(req: NextAuthRequest) {
|
||||
if (!slug)
|
||||
return NextResponse.redirect(`${siteConfig.url}/docs/short-urls`, 302);
|
||||
|
||||
const geo = geolocation(req);
|
||||
const headers = req.headers;
|
||||
const { browser, device } = parseUserAgent(headers.get("user-agent") || "");
|
||||
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") || "";
|
||||
@@ -37,19 +40,25 @@ async function handleShortUrl(req: NextAuthRequest) {
|
||||
const trackingData = {
|
||||
slug,
|
||||
referer: headers.get("referer") || "(None)",
|
||||
ip: headers.get("X-Forwarded-For"),
|
||||
ip,
|
||||
city: geo?.city,
|
||||
region: geo?.region,
|
||||
country: geo?.country,
|
||||
latitude: geo?.latitude,
|
||||
longitude: geo?.longitude,
|
||||
flag: geo?.flag,
|
||||
lang: headers.get("accept-language")?.split(",")[0],
|
||||
device: device.model || "Unknown",
|
||||
browser: browser.name || "Unknown",
|
||||
lang: headers.get("accept-language")?.split(",")[0] || "Unknown",
|
||||
device: ua.device.model || "Unknown",
|
||||
browser: ua.browser.name || "Unknown",
|
||||
engine: ua.engine.name || "",
|
||||
os: ua.os.name || "",
|
||||
cpu: ua.cpu.architecture || "",
|
||||
isBot: ua.isBot,
|
||||
password,
|
||||
};
|
||||
|
||||
// console.log("Tracking data:", trackingData, siteConfig.url);
|
||||
|
||||
const res = await fetch(`${siteConfig.url}/api/s`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -90,22 +99,11 @@ async function handleShortUrl(req: NextAuthRequest) {
|
||||
return NextResponse.redirect(target, 302);
|
||||
}
|
||||
|
||||
// 提取 slug
|
||||
function extractSlug(url: string): string | null {
|
||||
const match = url.match(/([^/?]+)(?:\?.*)?$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
// 解析用户代理
|
||||
const parser = new UAParser();
|
||||
function parseUserAgent(ua: string) {
|
||||
parser.setUA(ua);
|
||||
return {
|
||||
browser: parser.getBrowser(),
|
||||
device: parser.getDevice(),
|
||||
};
|
||||
}
|
||||
|
||||
export default auth(async (req) => {
|
||||
try {
|
||||
return await handleShortUrl(req);
|
||||
|
||||
@@ -6,6 +6,7 @@ import("./env.mjs");
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
output: "standalone",
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
12
package.json
12
package.json
@@ -11,12 +11,18 @@
|
||||
"postbuild": "next-sitemap",
|
||||
"turbo": "next dev --turbo",
|
||||
"start": "next start",
|
||||
"start-docker": "npm-run-all check-db start-server",
|
||||
"start-server": "node server.js",
|
||||
"lint": "next lint",
|
||||
"preview": "next build && next start",
|
||||
"postinstall": "prisma generate",
|
||||
"db:push": "npx prisma migrate deploy",
|
||||
"email": "email dev --dir emails --port 3333",
|
||||
"remove-content": "node ./setup.mjs"
|
||||
"remove-content": "node ./setup.mjs",
|
||||
"check-db": "node scripts/check-db.js"
|
||||
},
|
||||
"prisma": {
|
||||
"schema": "./prisma/schema.prisma"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.4.1",
|
||||
@@ -68,6 +74,7 @@
|
||||
"@vercel/functions": "^1.4.0",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"chalk": "^4.1.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
@@ -77,6 +84,7 @@
|
||||
"d3-scale": "^4.0.2",
|
||||
"d3-scale-chromatic": "^3.1.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"framer-motion": "^12.5.0",
|
||||
"globe.gl": "^2.41.4",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -93,6 +101,7 @@
|
||||
"next-themes": "^0.3.0",
|
||||
"next-view-transitions": "^0.3.0",
|
||||
"nodemailer": "^6.9.14",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"peerjs": "^1.5.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "18.3.1",
|
||||
@@ -112,6 +121,7 @@
|
||||
"shiki": "^1.11.0",
|
||||
"sonner": "^1.5.0",
|
||||
"swr": "^2.2.5",
|
||||
"semver": "^7.5.4",
|
||||
"tailwind-merge": "^2.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"three": "^0.176.0",
|
||||
|
||||
232
pnpm-lock.yaml
generated
232
pnpm-lock.yaml
generated
@@ -152,6 +152,9 @@ importers:
|
||||
'@vercel/og':
|
||||
specifier: ^0.6.2
|
||||
version: 0.6.2
|
||||
chalk:
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.2
|
||||
cheerio:
|
||||
specifier: 1.0.0-rc.12
|
||||
version: 1.0.0-rc.12
|
||||
@@ -182,6 +185,9 @@ importers:
|
||||
date-fns:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
dotenv:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
framer-motion:
|
||||
specifier: ^12.5.0
|
||||
version: 12.5.0(@emotion/is-prop-valid@0.8.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -230,6 +236,9 @@ importers:
|
||||
nodemailer:
|
||||
specifier: ^6.9.14
|
||||
version: 6.9.14
|
||||
npm-run-all:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5
|
||||
peerjs:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
@@ -275,6 +284,9 @@ importers:
|
||||
resend:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
semver:
|
||||
specifier: ^7.5.4
|
||||
version: 7.6.3
|
||||
sharp:
|
||||
specifier: ^0.33.4
|
||||
version: 0.33.4
|
||||
@@ -4053,6 +4065,10 @@ packages:
|
||||
countup.js@2.8.0:
|
||||
resolution: {integrity: sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ==}
|
||||
|
||||
cross-spawn@6.0.6:
|
||||
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
|
||||
engines: {node: '>=4.8'}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4411,6 +4427,10 @@ packages:
|
||||
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dotenv@10.0.0:
|
||||
resolution: {integrity: sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
dotenv@16.0.3:
|
||||
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -5114,6 +5134,9 @@ packages:
|
||||
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
|
||||
html-to-text@9.0.5:
|
||||
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -5465,6 +5488,9 @@ packages:
|
||||
json-buffer@3.0.1:
|
||||
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
||||
|
||||
json-parse-better-errors@1.0.2:
|
||||
resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
|
||||
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
|
||||
@@ -5549,6 +5575,10 @@ packages:
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
load-json-file@4.0.0:
|
||||
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
loader-runner@4.3.0:
|
||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||
engines: {node: '>=6.11.5'}
|
||||
@@ -5635,10 +5665,6 @@ packages:
|
||||
lru-cache@5.1.1:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
lru-cache@6.0.0:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lucide-react@0.414.0:
|
||||
resolution: {integrity: sha512-Krr/MHg9AWoJc52qx8hyJ64X9++JNfS1wjaJviLM1EP/68VNB7Tv0VMldLCB1aUe6Ka9QxURPhQm/eB6cqOM3A==}
|
||||
peerDependencies:
|
||||
@@ -5742,6 +5768,10 @@ packages:
|
||||
resolution: {integrity: sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
||||
memorystream@0.3.1:
|
||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
|
||||
meow@12.1.1:
|
||||
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
|
||||
engines: {node: '>=16.10'}
|
||||
@@ -6034,6 +6064,9 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
nice-try@1.0.5:
|
||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||
|
||||
no-case@3.0.4:
|
||||
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
|
||||
|
||||
@@ -6052,6 +6085,9 @@ packages:
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
|
||||
normalize-package-data@2.5.0:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -6060,6 +6096,11 @@ packages:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
npm-run-all@4.1.5:
|
||||
resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
|
||||
engines: {node: '>= 4'}
|
||||
hasBin: true
|
||||
|
||||
npm-run-path@4.0.1:
|
||||
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6186,6 +6227,10 @@ packages:
|
||||
parse-entities@4.0.1:
|
||||
resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
|
||||
|
||||
parse-json@4.0.0:
|
||||
resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
parse-json@5.2.0:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6223,6 +6268,10 @@ packages:
|
||||
path-is-inside@1.0.2:
|
||||
resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==}
|
||||
|
||||
path-key@2.0.1:
|
||||
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6238,6 +6287,10 @@ packages:
|
||||
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
path-type@3.0.0:
|
||||
resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
path-type@4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6277,10 +6330,19 @@ packages:
|
||||
resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
pidtree@0.3.1:
|
||||
resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
pify@2.3.0:
|
||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
pify@3.0.0:
|
||||
resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
pify@4.0.1:
|
||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -6638,6 +6700,10 @@ packages:
|
||||
read-cache@1.0.0:
|
||||
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
|
||||
|
||||
read-pkg@3.0.0:
|
||||
resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
readable-stream@3.6.2:
|
||||
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -6854,13 +6920,12 @@ packages:
|
||||
selderee@0.11.0:
|
||||
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
||||
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
semver@5.7.2:
|
||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||
hasBin: true
|
||||
|
||||
semver@7.6.0:
|
||||
resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
|
||||
engines: {node: '>=10'}
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
|
||||
semver@7.6.3:
|
||||
@@ -6892,10 +6957,18 @@ packages:
|
||||
resolution: {integrity: sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==}
|
||||
engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
|
||||
shebang-command@1.2.0:
|
||||
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@1.0.0:
|
||||
resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7004,6 +7077,18 @@ packages:
|
||||
spawn-command@0.0.2:
|
||||
resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
|
||||
|
||||
spdx-correct@3.2.0:
|
||||
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
|
||||
|
||||
spdx-exceptions@2.5.0:
|
||||
resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
|
||||
|
||||
spdx-expression-parse@3.0.1:
|
||||
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
|
||||
|
||||
spdx-license-ids@3.0.21:
|
||||
resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
@@ -7034,6 +7119,10 @@ packages:
|
||||
resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string.prototype.padend@3.1.6:
|
||||
resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
string.prototype.repeat@1.0.0:
|
||||
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
|
||||
|
||||
@@ -7521,6 +7610,9 @@ packages:
|
||||
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
|
||||
hasBin: true
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
|
||||
vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -7707,9 +7799,6 @@ packages:
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
|
||||
yaml@1.10.2:
|
||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -9016,7 +9105,7 @@ snapshots:
|
||||
'@babel/traverse': 7.24.6
|
||||
'@babel/types': 7.24.6
|
||||
prettier: 3.3.3
|
||||
semver: 7.6.0
|
||||
semver: 7.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -11989,6 +12078,14 @@ snapshots:
|
||||
|
||||
countup.js@2.8.0: {}
|
||||
|
||||
cross-spawn@6.0.6:
|
||||
dependencies:
|
||||
nice-try: 1.0.5
|
||||
path-key: 2.0.1
|
||||
semver: 5.7.2
|
||||
shebang-command: 1.2.0
|
||||
which: 1.3.1
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -12370,6 +12467,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-obj: 2.0.0
|
||||
|
||||
dotenv@10.0.0: {}
|
||||
|
||||
dotenv@16.0.3: {}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
@@ -13361,6 +13460,8 @@ snapshots:
|
||||
|
||||
hex-rgb@4.3.0: {}
|
||||
|
||||
hosted-git-info@2.8.9: {}
|
||||
|
||||
html-to-text@9.0.5:
|
||||
dependencies:
|
||||
'@selderee/plugin-htmlparser2': 0.11.0
|
||||
@@ -13665,6 +13766,8 @@ snapshots:
|
||||
|
||||
json-buffer@3.0.1: {}
|
||||
|
||||
json-parse-better-errors@1.0.2: {}
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
@@ -13736,6 +13839,13 @@ snapshots:
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
load-json-file@4.0.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
parse-json: 4.0.0
|
||||
pify: 3.0.0
|
||||
strip-bom: 3.0.0
|
||||
|
||||
loader-runner@4.3.0: {}
|
||||
|
||||
loader-utils@2.0.4:
|
||||
@@ -13809,10 +13919,6 @@ snapshots:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
|
||||
lru-cache@6.0.0:
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
|
||||
lucide-react@0.414.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
@@ -14072,6 +14178,8 @@ snapshots:
|
||||
sonic-forest: 1.0.2(tslib@2.8.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
memorystream@0.3.1: {}
|
||||
|
||||
meow@12.1.1: {}
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
@@ -14531,6 +14639,8 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
nice-try@1.0.5: {}
|
||||
|
||||
no-case@3.0.4:
|
||||
dependencies:
|
||||
lower-case: 2.0.2
|
||||
@@ -14546,10 +14656,29 @@ snapshots:
|
||||
dependencies:
|
||||
abbrev: 2.0.0
|
||||
|
||||
normalize-package-data@2.5.0:
|
||||
dependencies:
|
||||
hosted-git-info: 2.8.9
|
||||
resolve: 1.22.8
|
||||
semver: 5.7.2
|
||||
validate-npm-package-license: 3.0.4
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
normalize-range@0.1.2: {}
|
||||
|
||||
npm-run-all@4.1.5:
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
chalk: 2.4.2
|
||||
cross-spawn: 6.0.6
|
||||
memorystream: 0.3.1
|
||||
minimatch: 3.1.2
|
||||
pidtree: 0.3.1
|
||||
read-pkg: 3.0.0
|
||||
shell-quote: 1.8.1
|
||||
string.prototype.padend: 3.1.6
|
||||
|
||||
npm-run-path@4.0.1:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -14699,6 +14828,11 @@ snapshots:
|
||||
is-decimal: 2.0.1
|
||||
is-hexadecimal: 2.0.1
|
||||
|
||||
parse-json@4.0.0:
|
||||
dependencies:
|
||||
error-ex: 1.3.2
|
||||
json-parse-better-errors: 1.0.2
|
||||
|
||||
parse-json@5.2.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.6
|
||||
@@ -14737,6 +14871,8 @@ snapshots:
|
||||
|
||||
path-is-inside@1.0.2: {}
|
||||
|
||||
path-key@2.0.1: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-key@4.0.0: {}
|
||||
@@ -14748,6 +14884,10 @@ snapshots:
|
||||
lru-cache: 10.0.2
|
||||
minipass: 7.0.4
|
||||
|
||||
path-type@3.0.0:
|
||||
dependencies:
|
||||
pify: 3.0.0
|
||||
|
||||
path-type@4.0.0: {}
|
||||
|
||||
pbf@3.3.0:
|
||||
@@ -14782,8 +14922,12 @@ snapshots:
|
||||
|
||||
picomatch@3.0.1: {}
|
||||
|
||||
pidtree@0.3.1: {}
|
||||
|
||||
pify@2.3.0: {}
|
||||
|
||||
pify@3.0.0: {}
|
||||
|
||||
pify@4.0.1: {}
|
||||
|
||||
pinkie-promise@2.0.1:
|
||||
@@ -15179,6 +15323,12 @@ snapshots:
|
||||
dependencies:
|
||||
pify: 2.3.0
|
||||
|
||||
read-pkg@3.0.0:
|
||||
dependencies:
|
||||
load-json-file: 4.0.0
|
||||
normalize-package-data: 2.5.0
|
||||
path-type: 3.0.0
|
||||
|
||||
readable-stream@3.6.2:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
@@ -15493,11 +15643,9 @@ snapshots:
|
||||
dependencies:
|
||||
parseley: 0.12.1
|
||||
|
||||
semver@6.3.1: {}
|
||||
semver@5.7.2: {}
|
||||
|
||||
semver@7.6.0:
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
semver@6.3.1: {}
|
||||
|
||||
semver@7.6.3: {}
|
||||
|
||||
@@ -15533,7 +15681,7 @@ snapshots:
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
detect-libc: 2.0.3
|
||||
semver: 7.6.0
|
||||
semver: 7.6.3
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.33.4
|
||||
'@img/sharp-darwin-x64': 0.33.4
|
||||
@@ -15555,10 +15703,16 @@ snapshots:
|
||||
'@img/sharp-win32-ia32': 0.33.4
|
||||
'@img/sharp-win32-x64': 0.33.4
|
||||
|
||||
shebang-command@1.2.0:
|
||||
dependencies:
|
||||
shebang-regex: 1.0.0
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@1.0.0: {}
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
shell-quote@1.8.1: {}
|
||||
@@ -15672,6 +15826,20 @@ snapshots:
|
||||
|
||||
spawn-command@0.0.2: {}
|
||||
|
||||
spdx-correct@3.2.0:
|
||||
dependencies:
|
||||
spdx-expression-parse: 3.0.1
|
||||
spdx-license-ids: 3.0.21
|
||||
|
||||
spdx-exceptions@2.5.0: {}
|
||||
|
||||
spdx-expression-parse@3.0.1:
|
||||
dependencies:
|
||||
spdx-exceptions: 2.5.0
|
||||
spdx-license-ids: 3.0.21
|
||||
|
||||
spdx-license-ids@3.0.21: {}
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
@@ -15711,6 +15879,13 @@ snapshots:
|
||||
set-function-name: 2.0.2
|
||||
side-channel: 1.0.6
|
||||
|
||||
string.prototype.padend@3.1.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
string.prototype.repeat@1.0.0:
|
||||
dependencies:
|
||||
define-properties: 1.2.1
|
||||
@@ -15721,19 +15896,19 @@ snapshots:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-abstract: 1.23.3
|
||||
es-object-atoms: 1.0.0
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
string.prototype.trimend@1.0.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.0.0
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
string.prototype.trimstart@1.0.8:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
define-properties: 1.2.1
|
||||
es-object-atoms: 1.0.0
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
string_decoder@1.3.0:
|
||||
dependencies:
|
||||
@@ -16257,6 +16432,11 @@ snapshots:
|
||||
|
||||
uuid@9.0.1: {}
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
dependencies:
|
||||
spdx-correct: 3.2.0
|
||||
spdx-expression-parse: 3.0.1
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vaul@0.9.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
@@ -16571,8 +16751,6 @@ snapshots:
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yallist@4.0.0: {}
|
||||
|
||||
yaml@1.10.2: {}
|
||||
|
||||
yaml@2.3.4: {}
|
||||
|
||||
BIN
public/_static/images/domains.png
Normal file
BIN
public/_static/images/domains.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
BIN
public/_static/images/realtime-globe.png
Normal file
BIN
public/_static/images/realtime-globe.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -1,39 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||
<url><loc>https://wr.do/robots.txt</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/authentification</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare-email-worker</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/components</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/config-files</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/database</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/email</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/installation</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/markdown-files</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/quick-start</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/dns-records</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/emails</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/cloudflare</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/other</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/vercel</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/zeabur</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/icon</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/markdown</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/meta-info</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/qrcode</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/screenshot</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/text</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/plan</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/quick-start</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/short-urls</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/wroom</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/pricing</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/privacy</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/terms</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/chat</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/manifest.json</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/password-prompt</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/opengraph-image.jpg</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/robots.txt</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/pricing</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/privacy</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/terms</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/authentification</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare-email-worker</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/components</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/config-files</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/database</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/email</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/installation</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/markdown-files</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/quick-start</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/dns-records</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/emails</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/cloudflare</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/other</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/vercel</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/zeabur</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/icon</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/markdown</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/meta-info</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/qrcode</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/screenshot</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/text</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/plan</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/quick-start</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/short-urls</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/wroom</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/chat</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/password-prompt</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/manifest.json</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/opengraph-image.jpg</loc><lastmod>2025-05-26T09:40:46.922Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
File diff suppressed because one or more lines are too long
96
scripts/check-db.js
Normal file
96
scripts/check-db.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/* eslint-disable no-console */
|
||||
require("dotenv").config();
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const chalk = require("chalk");
|
||||
const { execSync } = require("child_process");
|
||||
const semver = require("semver");
|
||||
|
||||
if (process.env.SKIP_DB_CHECK === "true") {
|
||||
console.log("Skipping database check.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function getDatabaseType(url = process.env.DATABASE_URL) {
|
||||
const type = url && url.split(":")[0];
|
||||
|
||||
if (type === "postgres") {
|
||||
return "postgresql";
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
function success(msg) {
|
||||
console.log(chalk.greenBright(`✓ ${msg}`));
|
||||
}
|
||||
|
||||
function error(msg) {
|
||||
console.log(chalk.redBright(`✗ ${msg}`));
|
||||
}
|
||||
|
||||
async function checkEnv() {
|
||||
if (!process.env.DATABASE_URL) {
|
||||
throw new Error("DATABASE_URL is not defined.");
|
||||
} else {
|
||||
success("DATABASE_URL is defined.");
|
||||
}
|
||||
}
|
||||
|
||||
async function checkConnection() {
|
||||
try {
|
||||
await prisma.$connect();
|
||||
|
||||
success("Database connection successful.");
|
||||
} catch (e) {
|
||||
throw new Error("Unable to connect to the database: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDatabaseVersion() {
|
||||
const query = await prisma.$queryRaw`select version() as version`;
|
||||
const version = semver.valid(semver.coerce(query[0].version));
|
||||
|
||||
const databaseType = getDatabaseType();
|
||||
const minVersion = databaseType === "postgresql" ? "9.4.0" : "5.7.0";
|
||||
|
||||
if (semver.lt(version, minVersion)) {
|
||||
throw new Error(
|
||||
`Database version is not compatible. Please upgrade ${databaseType} version to ${minVersion} or greater`,
|
||||
);
|
||||
}
|
||||
|
||||
success("Database version check successful.");
|
||||
}
|
||||
|
||||
async function applyMigration() {
|
||||
if (process.env.SKIP_DB_MIGRATION === "false") {
|
||||
console.log(execSync("prisma generate").toString());
|
||||
console.log(execSync("prisma migrate deploy").toString());
|
||||
|
||||
success("Database is up to date.");
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
let err = false;
|
||||
for (let fn of [
|
||||
checkEnv,
|
||||
checkConnection,
|
||||
checkDatabaseVersion,
|
||||
applyMigration,
|
||||
]) {
|
||||
try {
|
||||
await fn();
|
||||
} catch (e) {
|
||||
error(e.message);
|
||||
err = true;
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user