diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..347768a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +Dockerfile +.dockerignore +node_modules +.next +.git +.gitignore +*.md +.env* +!.env.example +uploads diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..de5e919 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/nanami_web?schema=public" +NEXTAUTH_SECRET="change-me-in-production-use-a-random-string" +NEXTAUTH_URL="http://localhost:3000" +ADMIN_USERNAME="admin" +ADMIN_PASSWORD="admin123" diff --git a/.gitignore b/.gitignore index 5ef6a52..b0d3aed 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,13 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example + +# deploy credentials +.deploy-config + +# uploads +/uploads # vercel .vercel @@ -39,3 +46,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +/src/generated/prisma diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..65b6f5f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM node:20-alpine AS base + +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npx prisma generate +RUN npm run build + +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/src/generated ./src/generated + +RUN mkdir .next +RUN chown nextjs:nodejs .next + +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +RUN mkdir -p /app/uploads && chown nextjs:nodejs /app/uploads + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/README.md b/README.md index e215bc4..ed71b6b 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,74 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Nanami Web - WoW Addon Platform -## Getting Started +World of Warcraft 插件发布与下载平台。 -First, run the development server: +## 技术栈 + +- **框架**: Next.js 16 (App Router) +- **语言**: TypeScript +- **UI**: TailwindCSS + shadcn/ui +- **数据库**: PostgreSQL + Prisma ORM +- **认证**: NextAuth.js (Credentials Provider) +- **部署**: Docker + docker-compose + +## 本地开发 + +### 前置条件 + +- Node.js 20+ +- PostgreSQL 16+ + +### 安装 + +```bash +npm install +cp .env.example .env +# 编辑 .env 中的 DATABASE_URL 和其他配置 +``` + +### 数据库设置 + +```bash +# 推送 schema 到数据库 +npm run db:push + +# 初始化管理员账号和示例数据 +npm run db:seed +``` + +### 启动开发服务器 ```bash npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +访问 http://localhost:3000 查看前台,http://localhost:3000/admin 进入后台管理。 -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +默认管理员账号:`admin` / `admin123` -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +## Docker 部署 -## Learn More +```bash +# 启动所有服务 +docker compose up -d -To learn more about Next.js, take a look at the following resources: +# 初始化数据库 +docker compose exec app npx prisma db push +docker compose exec app npx tsx prisma/seed.ts +``` -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## 项目结构 -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +``` +src/ +├── app/ +│ ├── (public)/ # 前台页面 +│ ├── admin/ # 后台管理 +│ └── api/ # API 路由 +├── components/ +│ ├── ui/ # shadcn/ui 组件 +│ ├── public/ # 前台组件 +│ └── admin/ # 后台组件 +├── lib/ # 工具库 +└── types/ # 类型定义 +``` diff --git a/deploy-init.sh b/deploy-init.sh new file mode 100755 index 0000000..a81edda --- /dev/null +++ b/deploy-init.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CONFIG_FILE="$SCRIPT_DIR/.deploy-config" + +if [ ! -f "$CONFIG_FILE" ]; then + echo "❌ 缺少 .deploy-config 文件" + exit 1 +fi + +source "$CONFIG_FILE" + +if ! command -v sshpass &> /dev/null; then + echo "❌ 未安装 sshpass,请先运行: brew install hudochenkov/sshpass/sshpass" + exit 1 +fi + +PASS_FILE=$(mktemp) +echo "$SERVER_PASS" > "$PASS_FILE" +trap "rm -f $PASS_FILE" EXIT + +SSH_OPTS="-o StrictHostKeyChecking=no" + +run_ssh() { + sshpass -f "$PASS_FILE" ssh $SSH_OPTS "${SERVER_USER}@${SERVER_HOST}" "$@" +} + +NEXTAUTH_SECRET="$(openssl rand -base64 32)" + +echo "" +echo "=========================================" +echo " 首次初始化服务器 ${SERVER_HOST}" +echo "=========================================" +echo "" + +echo "=> 创建项目目录和 uploads..." +run_ssh "mkdir -p ${REMOTE_DIR}/uploads" + +echo "" +echo "=> 创建 .env 文件..." +run_ssh "cat > ${REMOTE_DIR}/.env << 'EOF' +DATABASE_URL=postgresql://nanami:Scl%40qq.com1@localhost:5432/nanamiweb?schema=public +NEXTAUTH_SECRET=${NEXTAUTH_SECRET} +NEXTAUTH_URL=http://${SERVER_HOST}:3000 +ADMIN_USERNAME=admin +ADMIN_PASSWORD=admin123 +EOF" + +echo "" +echo "=> 验证 .env 文件..." +run_ssh "cat ${REMOTE_DIR}/.env" + +echo "" +echo "=========================================" +echo " ✅ 初始化完成!" +echo "=========================================" +echo "" +echo "接下来运行: ./deploy-remote.sh" +echo "" +echo "⚠️ 部署成功后记得修改管理员密码:" +echo " 访问 http://${SERVER_HOST}:3000/admin/login" +echo " 默认账号: admin / admin123" +echo " 登录后在「系统设置」中修改密码" +echo "" diff --git a/deploy-remote.sh b/deploy-remote.sh new file mode 100755 index 0000000..be0f29d --- /dev/null +++ b/deploy-remote.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CONFIG_FILE="$SCRIPT_DIR/.deploy-config" + +if [ ! -f "$CONFIG_FILE" ]; then + echo "❌ 缺少配置文件 .deploy-config" + exit 1 +fi + +source "$CONFIG_FILE" + +if ! command -v sshpass &> /dev/null; then + echo "❌ 未安装 sshpass,请先运行: brew install hudochenkov/sshpass/sshpass" + exit 1 +fi + +PASS_FILE=$(mktemp) +echo "$SERVER_PASS" > "$PASS_FILE" +trap "rm -f $PASS_FILE /tmp/nanami-web-deploy.tar.gz" EXIT + +SSH_OPTS="-o StrictHostKeyChecking=no" + +run_ssh() { + sshpass -f "$PASS_FILE" ssh $SSH_OPTS "${SERVER_USER}@${SERVER_HOST}" "source /etc/profile 2>/dev/null; source ~/.bashrc 2>/dev/null; source ~/.bash_profile 2>/dev/null; $*" +} + +run_scp() { + sshpass -f "$PASS_FILE" scp $SSH_OPTS "$@" +} + +echo "" +echo "=========================================" +echo " 部署到 ${SERVER_HOST}" +echo "=========================================" +echo "" + +echo "=> [1/6] 本地安装依赖..." +cd "$SCRIPT_DIR" +npm install --silent + +echo "" +echo "=> [2/6] 本地同步 Prisma..." +npx prisma generate + +echo "" +echo "=> [3/6] 本地构建项目..." +npm run build + +echo "" +echo "=> [4/6] 打包构建产物..." +TAR_FILE="/tmp/nanami-web-deploy.tar.gz" +STAGE_DIR="/tmp/nanami-web-stage" +rm -rf "$STAGE_DIR" + +STANDALONE_ROOT="$SCRIPT_DIR/.next/standalone/gitLib/nanami-web" +if [ ! -f "$STANDALONE_ROOT/server.js" ]; then + STANDALONE_ROOT="$SCRIPT_DIR/.next/standalone" +fi + +mkdir -p "$STAGE_DIR/.next/standalone" +cp -r "$STANDALONE_ROOT/"* "$STAGE_DIR/.next/standalone/" +cp -r "$STANDALONE_ROOT/.next" "$STAGE_DIR/.next/standalone/" +mkdir -p "$STAGE_DIR/.next/standalone/.next/static" +cp -r "$SCRIPT_DIR/.next/static/"* "$STAGE_DIR/.next/standalone/.next/static/" +cp -r "$SCRIPT_DIR/public" "$STAGE_DIR/.next/standalone/" + +cp -r "$SCRIPT_DIR/prisma" "$STAGE_DIR/" +cp "$SCRIPT_DIR/package.json" "$STAGE_DIR/" +cp "$SCRIPT_DIR/package-lock.json" "$STAGE_DIR/" +cp "$SCRIPT_DIR/ecosystem.config.js" "$STAGE_DIR/" + +tar -czf "$TAR_FILE" -C "$STAGE_DIR" . +rm -rf "$STAGE_DIR" + +echo " 打包完成: $(du -h "$TAR_FILE" | cut -f1)" + +echo "" +echo "=> [5/6] 上传到服务器..." +run_ssh "mkdir -p ${REMOTE_DIR} && rm -rf ${REMOTE_DIR}/.next" +run_scp "$TAR_FILE" "${SERVER_USER}@${SERVER_HOST}:/tmp/nanami-web-deploy.tar.gz" +run_ssh "cd ${REMOTE_DIR} && tar -xzf /tmp/nanami-web-deploy.tar.gz && rm -f /tmp/nanami-web-deploy.tar.gz" + +echo "" +echo "=> [6/6] 服务器安装生产依赖 & 同步数据库 & 重启..." +run_ssh "cd ${REMOTE_DIR} && \ + npm install --omit=dev --registry=https://registry.npmjs.org && \ + npx prisma generate && \ + npx prisma db push && \ + mkdir -p uploads && \ + ln -sf ${REMOTE_DIR}/uploads ${REMOTE_DIR}/.next/standalone/uploads && \ + ln -sf ${REMOTE_DIR}/.env ${REMOTE_DIR}/.next/standalone/.env && \ + ln -sf ${REMOTE_DIR}/src/generated ${REMOTE_DIR}/.next/standalone/src/generated 2>/dev/null; \ + cd ${REMOTE_DIR} && (pm2 delete nanami-web 2>/dev/null; pm2 start ecosystem.config.js)" + +echo "" +echo "=========================================" +echo " ✅ 部署完成!" +echo "=========================================" +echo "" +echo "网站地址: http://${SERVER_HOST}:3000" +echo "管理后台: http://${SERVER_HOST}:3000/admin/login" +echo "" diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..9c246ab --- /dev/null +++ b/deploy.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")" +echo "=> 拉取最新代码..." +git pull + +echo "=> 安装依赖..." +npm install + +echo "=> 同步数据库..." +npx prisma generate +npx prisma db push + +echo "=> 构建项目..." +npm run build + +echo "=> 准备运行目录..." +cp -r .next/static .next/standalone/.next/static +cp -r public .next/standalone/public +mkdir -p uploads +ln -sf "$(pwd)/uploads" .next/standalone/uploads + +echo "=> 重启应用..." +pm2 restart nanami-web || pm2 start ecosystem.config.js + +echo "=> 部署完成!" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7563a77 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: nanami_web + volumes: + - pg_data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + app: + build: . + restart: unless-stopped + ports: + - "3000:3000" + environment: + DATABASE_URL: postgresql://postgres:postgres@db:5432/nanami_web?schema=public + NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-change-me-in-production} + NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} + volumes: + - uploads_data:/app/uploads + depends_on: + db: + condition: service_healthy + +volumes: + pg_data: + uploads_data: diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..d59b553 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,529 @@ +# Nanami API 文档 + +所有公开接口均无需认证,供客户端软件直接调用。 + +基础 URL: `https://nui.rucky.cn` + +--- + +# 一、插件市场 API + +供启动器拉取插件列表、详情、版本和下载。 + +--- + +## 1.1 获取插件列表 + +### 请求 + +``` +GET /api/addons +``` + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|---|---|---|---| +| category | string | 否 | 按分类筛选(如 `ui`, `quest`, `combat`, `general`) | +| search | string | 否 | 按名称或简介模糊搜索 | +| published | string | 否 | 默认 `true` 只返回已发布插件,传 `false` 返回全部 | + +### 响应示例 + +```json +[ + { + "id": "cxxx001", + "name": "Nanami", + "slug": "nanami", + "summary": "一站式乌龟服插件管理器", + "description": "详细描述...", + "iconUrl": "/uploads/nanami-icon.png", + "category": "general", + "published": true, + "totalDownloads": 1234, + "createdAt": "2026-01-01T00:00:00.000Z", + "updatedAt": "2026-03-18T10:00:00.000Z", + "releases": [ + { + "id": "rel001", + "version": "1.0.0", + "isLatest": true + } + ], + "_count": { + "releases": 3 + } + } +] +``` + +### 响应字段说明 + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | string | 插件唯一 ID | +| name | string | 插件名称 | +| slug | string | 插件标识(URL 友好) | +| summary | string | 简短描述 | +| description | string | 详细描述(支持长文本) | +| iconUrl | string\|null | 图标地址 | +| category | string | 分类标签 | +| published | boolean | 是否已发布 | +| totalDownloads | int | 累计下载次数 | +| releases | array | 最新版本信息(仅包含 isLatest=true 的版本) | +| _count.releases | int | 版本总数 | + +--- + +## 1.2 获取插件详情 + +通过 ID 或 slug 获取单个插件的完整信息,包含所有版本和截图。 + +### 请求 + +``` +GET /api/addons/{id_or_slug} +``` + +### 参数 + +| 参数 | 说明 | +|---|---| +| id_or_slug | 插件 ID 或 slug,如 `nanami` | + +### 响应示例 + +```json +{ + "id": "cxxx001", + "name": "Nanami", + "slug": "nanami", + "summary": "一站式乌龟服插件管理器", + "description": "详细的 Markdown 描述...", + "iconUrl": "/uploads/nanami-icon.png", + "category": "general", + "published": true, + "totalDownloads": 1234, + "createdAt": "2026-01-01T00:00:00.000Z", + "updatedAt": "2026-03-18T10:00:00.000Z", + "releases": [ + { + "id": "rel003", + "addonId": "cxxx001", + "version": "1.2.0", + "changelog": "- 新增XX功能\n- 修复YY问题", + "downloadType": "local", + "filePath": "uploads/nanami-1.2.0.zip", + "externalUrl": null, + "gameVersion": "1.12.1", + "downloadCount": 500, + "isLatest": true, + "createdAt": "2026-03-18T10:00:00.000Z" + }, + { + "id": "rel002", + "addonId": "cxxx001", + "version": "1.1.0", + "changelog": "- 修复界面问题", + "downloadType": "local", + "filePath": "uploads/nanami-1.1.0.zip", + "externalUrl": null, + "gameVersion": "1.12.1", + "downloadCount": 300, + "isLatest": false, + "createdAt": "2026-02-15T10:00:00.000Z" + } + ], + "screenshots": [ + { + "id": "ss001", + "addonId": "cxxx001", + "imageUrl": "/uploads/screenshot1.png", + "sortOrder": 0 + } + ] +} +``` + +### 错误响应 + +| HTTP 状态码 | 说明 | +|---|---| +| 404 | 插件不存在 | + +--- + +## 1.3 获取版本列表 + +获取所有版本,可按插件 ID 筛选。 + +### 请求 + +``` +GET /api/releases?addonId={addonId} +``` + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|---|---|---|---| +| addonId | string | 否 | 按插件 ID 筛选 | + +### 响应示例 + +```json +[ + { + "id": "rel003", + "addonId": "cxxx001", + "version": "1.2.0", + "changelog": "- 新增XX功能", + "downloadType": "local", + "filePath": "uploads/nanami-1.2.0.zip", + "externalUrl": null, + "gameVersion": "1.12.1", + "downloadCount": 500, + "isLatest": true, + "createdAt": "2026-03-18T10:00:00.000Z", + "addon": { + "id": "cxxx001", + "name": "Nanami", + "slug": "nanami" + } + } +] +``` + +### 版本字段说明 + +| 字段 | 类型 | 说明 | +|---|---|---| +| id | string | 版本唯一 ID | +| addonId | string | 所属插件 ID | +| version | string | 版本号(如 "1.2.0") | +| changelog | string | 更新日志 | +| downloadType | string | `local` 本地文件 / `url` 外部链接 | +| gameVersion | string | 适配的游戏版本(如 "1.12.1") | +| downloadCount | int | 下载次数 | +| isLatest | boolean | 是否为最新版本 | +| addon | object | 所属插件基本信息 | + +--- + +## 1.4 下载插件 + +通过版本 ID 下载对应的插件压缩包。 + +### 请求 + +``` +GET /api/download/{releaseId} +``` + +### 行为 + +- **本地文件**:返回文件二进制流,`Content-Disposition` 设为 `attachment` +- **外部链接**:302 重定向到外部下载地址 +- 每次调用自动递增版本下载计数和插件总下载计数 + +### 响应头(本地文件时) + +``` +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="nanami-1.2.0.zip" +Content-Length: 1048576 +``` + +### 错误响应 + +| HTTP 状态码 | 说明 | +|---|---| +| 404 | 版本不存在或文件丢失 | + +--- + +# 二、软件更新 API + +供启动器检查自身版本更新和热更新。 + +--- + +## 软件标识(slug) + +| slug | 用途 | 文件类型 | +|---|---|---| +| `nanami-launcher` | 启动器全量安装包 | Nanami-Launcher-x.x.x.exe | +| `nanami-launcher-patch` | 热更新补丁包 | app.asar | + +--- + +## 2.1 检查更新 + +客户端启动时调用此接口,传入当前版本号以检测是否有新版本可用。 + +### 请求 + +``` +GET /api/software/check-update?slug={slug}&versionCode={versionCode} +``` + +### 参数 + +| 参数 | 类型 | 必填 | 说明 | +|---|---|---|---| +| slug | string | 是 | 软件标识 | +| versionCode | int | 否 | 当前客户端的版本代码(整数),不传默认为 0 | + +### 响应示例 + +**有更新:** + +```json +{ + "hasUpdate": true, + "forceUpdate": false, + "latest": { + "version": "1.2.0", + "versionCode": 120, + "changelog": "- 新增XX功能\n- 修复YY问题", + "downloadUrl": "https://nui.rucky.cn/api/software/download/cxxx123", + "fileSize": 5242880, + "minVersion": "1.0.0", + "createdAt": "2026-03-18T10:00:00.000Z" + } +} +``` + +**无更新:** + +```json +{ + "hasUpdate": false, + "forceUpdate": false, + "latest": { + "version": "1.0.0", + "versionCode": 100, + "changelog": "...", + "downloadUrl": "https://nui.rucky.cn/api/software/download/cxxx123", + "fileSize": 5242880, + "minVersion": null, + "createdAt": "2026-03-01T10:00:00.000Z" + } +} +``` + +### 响应字段说明 + +| 字段 | 类型 | 说明 | +|---|---|---| +| hasUpdate | boolean | 是否有新版本 | +| forceUpdate | boolean | 是否强制更新(true 时客户端应阻止用户继续使用旧版) | +| latest.version | string | 最新版本号(如 "1.2.0") | +| latest.versionCode | int | 最新版本代码(整数,用于比较大小) | +| latest.changelog | string | 更新日志 | +| latest.downloadUrl | string | 下载地址(直接用于下载) | +| latest.fileSize | int | 文件大小(字节) | +| latest.minVersion | string\|null | 最低兼容版本号 | +| latest.createdAt | string | 发布时间 (ISO 8601) | + +### 错误响应 + +| HTTP 状态码 | 说明 | +|---|---| +| 400 | 缺少 slug 参数 | +| 404 | 软件不存在或暂无版本 | + +--- + +## 2.2 下载软件包 + +通过版本 ID 下载对应的软件安装包。一般从"检查更新"接口返回的 `downloadUrl` 直接获取。 + +### 请求 + +``` +GET /api/software/download/{versionId} +``` + +### 行为 + +- **本地文件**:返回文件二进制流,`Content-Disposition` 设为 `attachment` +- **外部链接**:302 重定向到外部下载地址 +- 每次调用自动递增下载计数 + +### 响应头(本地文件时) + +``` +Content-Type: application/octet-stream +Content-Disposition: attachment; filename="nanami-launcher-v1.2.0.exe" +Content-Length: 5242880 +``` + +### 错误响应 + +| HTTP 状态码 | 说明 | +|---|---| +| 404 | 版本不存在或文件丢失 | + +--- + +## 2.3 首页启动器最新版本下载 + +直接下载 `nanami-launcher` 的最新版本,用于首页下载按钮。 + +### 请求 + +``` +GET /api/software/latest +``` + +下载最新全量包(自动递增下载计数)。 + +``` +GET /api/software/latest?info=1 +``` + +仅返回版本信息 JSON,不下载。 + +--- + +## 2.4 客户端更新检查流程 + +Nanami 启动器在启动后应按以下顺序检查更新: + +``` +客户端启动(当前 versionCode = 76) +│ +├── 步骤 1: 检查全量更新 +│ GET /api/software/check-update?slug=nanami-launcher&versionCode=76 +│ │ +│ ├── forceUpdate=true → 弹出强制更新对话框,下载完整安装包 +│ │ 不再检查热更新 +│ │ +│ └── hasUpdate=true, forceUpdate=false → 显示"新版本"徽章(用户自行决定) +│ │ +│ └── 继续步骤 2 +│ +├── 步骤 2: 检查热更新 +│ GET /api/software/check-update?slug=nanami-launcher-patch&versionCode=76 +│ │ +│ ├── hasUpdate=true → 静默下载 app.asar,替换本地文件 +│ │ 显示"就绪"徽章,提示重启生效 +│ │ +│ └── hasUpdate=false → 当前已是最新,无需操作 +│ +└── 正常运行 +``` + +### 版本号对应关系示例 + +| 发布类型 | 版本号 | versionCode | slug | +|---|---|---|---| +| 全量安装包 | 0.7.6 | 76 | nanami-launcher | +| 热更新补丁 | 0.7.7 | 77 | nanami-launcher-patch | +| 全量大版本 | 0.8.0 | 80 | nanami-launcher | + +### 后台操作 + +- **发布全量版本**:在「Nanami 启动器(全量包)」中上传 `.exe` 文件 +- **发布热更新**:在「Nanami 热更新包」中上传 `app.asar` 文件 +- 两者的 `versionCode` 应保持递增关系,以便客户端正确比较 + +--- + +--- + +# 三、客户端集成示例(Electron) + +## 3.1 启动器自身更新 + +```javascript +const BASE_URL = "https://nui.rucky.cn/api/software/check-update"; +const CURRENT_VERSION_CODE = 76; + +async function checkUpdates() { + // 步骤 1: 检查全量更新 + const fullResp = await fetch( + `${BASE_URL}?slug=nanami-launcher&versionCode=${CURRENT_VERSION_CODE}` + ); + const fullData = await fullResp.json(); + + if (fullData.hasUpdate && fullData.forceUpdate) { + showForceUpdateDialog(fullData.latest); + return; + } + + if (fullData.hasUpdate) { + showUpdateBadge(fullData.latest); + } + + // 步骤 2: 检查热更新 + const patchResp = await fetch( + `${BASE_URL}?slug=nanami-launcher-patch&versionCode=${CURRENT_VERSION_CODE}` + ); + const patchData = await patchResp.json(); + + if (patchData.hasUpdate) { + const asarResp = await fetch(patchData.latest.downloadUrl); + const buffer = await asarResp.arrayBuffer(); + // 写入本地 app.asar 路径... + showRestartBadge(); + } +} +``` + +## 3.2 插件市场拉取 + +```javascript +const API_BASE = "https://nui.rucky.cn"; + +// 获取全部已发布插件 +async function fetchAddons(category?: string, search?: string) { + const params = new URLSearchParams(); + if (category) params.set("category", category); + if (search) params.set("search", search); + + const resp = await fetch(`${API_BASE}/api/addons?${params}`); + return resp.json(); +} + +// 获取单个插件详情(含所有版本和截图) +async function fetchAddonDetail(slug: string) { + const resp = await fetch(`${API_BASE}/api/addons/${slug}`); + return resp.json(); +} + +// 下载插件最新版本 +async function downloadAddon(addon: any) { + const latestRelease = addon.releases?.find((r: any) => r.isLatest); + if (!latestRelease) return; + + const downloadUrl = `${API_BASE}/api/download/${latestRelease.id}`; + const resp = await fetch(downloadUrl); + const buffer = await resp.arrayBuffer(); + // 保存到 WoW 插件目录 Interface/AddOns/... +} + +// 检查插件是否有更新(对比本地已安装版本) +async function checkAddonUpdates(installedAddons: { slug: string; version: string }[]) { + const addons = await fetchAddons(); + const updates = []; + + for (const installed of installedAddons) { + const remote = addons.find((a: any) => a.slug === installed.slug); + if (!remote) continue; + + const latestRelease = remote.releases?.[0]; + if (latestRelease && latestRelease.version !== installed.version) { + updates.push({ + addon: remote, + currentVersion: installed.version, + newVersion: latestRelease.version, + }); + } + } + + return updates; +} +``` diff --git a/docs/DEPLOY-BAOTA.md b/docs/DEPLOY-BAOTA.md new file mode 100644 index 0000000..3f101e6 --- /dev/null +++ b/docs/DEPLOY-BAOTA.md @@ -0,0 +1,309 @@ +# 阿里云宝塔面板部署文档 + +本文档介绍如何在阿里云 ECS + 宝塔面板环境中部署 nanami-web 项目。 + +--- + +## 方案概览 + +``` +用户浏览器 --> Nginx (宝塔管理,SSL) --> Node.js (PM2 守护) --> PostgreSQL +``` + +- **Nginx**: 由宝塔面板管理,负责反向代理和 SSL +- **Node.js**: 使用 PM2 进程守护运行 Next.js standalone 产物 +- **PostgreSQL**: 通过宝塔面板安装,或使用 Docker + +--- + +## 一、环境准备 + +### 1.1 宝塔面板安装软件 + +在宝塔面板「软件商店」中安装: + +- **Nginx**(推荐最新稳定版) +- **PostgreSQL 16**(或通过 Docker 安装) +- **PM2 管理器**(Node.js 进程管理) +- **Node.js 版本管理器**,并安装 **Node.js 20+** + +### 1.2 创建数据库 + +1. 进入宝塔面板 → 数据库 → PostgreSQL +2. 添加数据库: + - 数据库名:`nanamiweb`(宝塔不允许包含下划线等特殊字符) + - 用户名:`nanami` + - 密码:自行设定,建议只用字母和数字(记住用于后续配置) + +如果宝塔不支持 PostgreSQL 管理界面,可通过终端操作: + +```bash +# 切换到 postgres 用户 +sudo -u postgres psql + +# 创建用户和数据库 +CREATE USER nanami WITH PASSWORD 'your_password_here'; +CREATE DATABASE nanamiweb OWNER nanami; +\q +``` + +--- + +## 二、部署项目 + +### 2.1 上传代码 + +```bash +# 在服务器上克隆代码(或通过宝塔面板上传 zip) +cd /www/wwwroot +git clone nanami-web +cd nanami-web +``` + +### 2.2 安装依赖并构建 + +```bash +# 安装 Node.js 依赖 +npm install + +# 创建环境变量文件 +cp .env.example .env +``` + +### 2.3 配置环境变量 + +编辑 `/www/wwwroot/nanami-web/.env`: + +```env +DATABASE_URL="postgresql://nanami:your_password_here@localhost:5432/nanamiweb?schema=public" +NEXTAUTH_SECRET="生成一个随机字符串" +NEXTAUTH_URL="https://your-domain.com" +ADMIN_USERNAME="admin" +ADMIN_PASSWORD="your_admin_password" +``` + +生成随机密钥: + +```bash +openssl rand -base64 32 +``` + +### 2.4 初始化数据库 + +```bash +# 推送表结构 +npx prisma db push + +# 初始化管理员账号 +npx tsx prisma/seed.ts +``` + +### 2.5 构建项目 + +```bash +npm run build +``` + +构建完成后会在 `.next/standalone` 目录生成独立的 Node.js 应用。 + +### 2.6 准备运行目录 + +```bash +# 复制静态文件到 standalone 目录 +cp -r .next/static .next/standalone/.next/static +cp -r public .next/standalone/public + +# 创建上传目录 +mkdir -p .next/standalone/uploads + +# 创建软链接使 uploads 共享 +ln -sf /www/wwwroot/nanami-web/uploads .next/standalone/uploads +``` + +--- + +## 三、使用 PM2 启动应用 + +### 3.1 创建 PM2 配置文件 + +在项目根目录创建 `ecosystem.config.js`: + +```javascript +module.exports = { + apps: [ + { + name: "nanami-web", + script: ".next/standalone/server.js", + cwd: "/www/wwwroot/nanami-web", + env: { + NODE_ENV: "production", + PORT: 3000, + HOSTNAME: "0.0.0.0", + }, + instances: 1, + autorestart: true, + max_memory_restart: "512M", + }, + ], +}; +``` + +### 3.2 启动应用 + +**方式一:通过宝塔 PM2 管理器** + +1. 进入宝塔面板 → 软件商店 → PM2 管理器 +2. 添加项目: + - 项目目录:`/www/wwwroot/nanami-web` + - 启动文件:`.next/standalone/server.js` + - 项目名称:`nanami-web` + +**方式二:通过命令行** + +```bash +cd /www/wwwroot/nanami-web +pm2 start ecosystem.config.js +pm2 save +pm2 startup # 设置开机自启 +``` + +### 3.3 验证应用运行 + +```bash +curl http://localhost:3000 +# 应返回 HTML 内容 +``` + +--- + +## 四、配置 Nginx 反向代理 + +### 4.1 宝塔面板创建网站 + +1. 进入宝塔面板 → 网站 → 添加站点 +2. 域名:填写你的域名(如 `addon.example.com`) +3. PHP 版本:选择「纯静态」 +4. 不创建数据库 + +### 4.2 配置反向代理 + +进入网站设置 → 反向代理 → 添加反向代理: + +- 代理名称:`nanami` +- 目标 URL:`http://127.0.0.1:3000` +- 发送域名:`$host` + +或手动编辑 Nginx 配置,在 `server` 块中添加: + +```nginx +location / { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 上传文件大小限制(根据需要调整) + client_max_body_size 100m; +} +``` + +### 4.3 配置 SSL + +1. 进入网站设置 → SSL +2. 选择 Let's Encrypt → 申请证书 +3. 开启「强制 HTTPS」 + +### 4.4 阿里云安全组 + +确保阿里云安全组放行以下端口: + +| 端口 | 用途 | +|---|---| +| 80 | HTTP | +| 443 | HTTPS | +| 22 | SSH | + +> 注意:3000 端口**不需要**对外开放,Nginx 通过内部代理访问。 + +--- + +## 五、更新部署 + +当代码有更新时: + +```bash +cd /www/wwwroot/nanami-web + +# 拉取最新代码 +git pull + +# 安装依赖(如有新依赖) +npm install + +# 同步数据库变更(如有 schema 变化) +npx prisma db push + +# 重新构建 +npm run build + +# 复制静态文件 +cp -r .next/static .next/standalone/.next/static +cp -r public .next/standalone/public +ln -sf /www/wwwroot/nanami-web/uploads .next/standalone/uploads + +# 重启应用 +pm2 restart nanami-web +``` + +可以将上述步骤写成脚本 `deploy.sh`: + +```bash +#!/bin/bash +set -e +cd /www/wwwroot/nanami-web +git pull +npm install +npx prisma db push +npm run build +cp -r .next/static .next/standalone/.next/static +cp -r public .next/standalone/public +ln -sf /www/wwwroot/nanami-web/uploads .next/standalone/uploads +pm2 restart nanami-web +echo "部署完成" +``` + +--- + +## 六、常见问题 + +### Q: 上传的文件在哪里? + +上传的文件存储在 `/www/wwwroot/nanami-web/uploads/` 目录。请确保定期备份。 + +### Q: 如何备份数据库? + +```bash +pg_dump -U nanami nanami_web > backup_$(date +%Y%m%d).sql +``` + +可在宝塔面板的「计划任务」中添加定时备份。 + +### Q: 忘记管理员密码? + +重新运行 seed 脚本(会用 .env 中的密码重置): + +```bash +cd /www/wwwroot/nanami-web +npx tsx prisma/seed.ts +``` + +### Q: 如何查看应用日志? + +```bash +pm2 logs nanami-web +pm2 logs nanami-web --lines 100 # 查看最近100行 +``` diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..8280c92 --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,20 @@ +const path = require("path"); + +module.exports = { + apps: [ + { + name: "nanami-web", + script: "server.js", + cwd: path.join(__dirname, ".next", "standalone"), + exec_mode: "fork", + env: { + NODE_ENV: "production", + PORT: 3000, + HOSTNAME: "0.0.0.0", + }, + instances: 1, + autorestart: true, + max_memory_restart: "512M", + }, + ], +}; diff --git a/next.config.ts b/next.config.ts index e9ffa30..1e844e0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,13 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + output: "standalone", + serverExternalPackages: ["bcryptjs", "@prisma/adapter-pg", "pg"], + images: { + remotePatterns: [ + { protocol: "https", hostname: "**" }, + ], + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 425a3b1..b819147 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,26 +7,39 @@ "": { "name": "nanami-web", "version": "0.1.0", + "hasInstallScript": true, "dependencies": { "@base-ui/react": "^1.3.0", + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.577.0", "next": "16.1.7", + "next-auth": "^5.0.0-beta.30", + "next-themes": "^0.4.6", + "pg": "^8.20.0", "react": "19.2.3", "react-dom": "19.2.3", "shadcn": "^4.0.8", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", + "@types/pg": "^8.18.0", "@types/react": "^19", "@types/react-dom": "^19", + "dotenv": "^17.3.1", "eslint": "^9", "eslint-config-next": "16.1.7", + "prisma": "^7.5.0", "tailwindcss": "^4", + "tsx": "^4.21.0", "typescript": "^5" } }, @@ -43,6 +56,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@auth/core": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.0.tgz", + "integrity": "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -510,6 +552,43 @@ } } }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/@dotenvx/dotenvx": { "version": "1.55.1", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.55.1.tgz", @@ -707,6 +786,36 @@ "@noble/ciphers": "^1.0.0" } }, + "node_modules/@electric-sql/pglite": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", + "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@electric-sql/pglite-socket": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz", + "integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" + }, + "peerDependencies": { + "@electric-sql/pglite": "0.3.15" + } + }, + "node_modules/@electric-sql/pglite-tools": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz", + "integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==", + "devOptional": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.3.15" + } + }, "node_modules/@emnapi/core": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", @@ -740,6 +849,448 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1642,6 +2193,20 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/@mrleebo/prisma-ast": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz", + "integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.5.0", + "lilconfig": "^2.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@mswjs/interceptors": { "version": "0.41.3", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", @@ -1922,6 +2487,286 @@ "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", "license": "MIT" }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@prisma/adapter-pg": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.5.0.tgz", + "integrity": "sha512-EJx7OLULahcC3IjJgdx2qRDNCT+ToY2v66UkeETMCLhNOTgqVzRzYvOEphY7Zp0eHyzfkC33Edd/qqeadf9R4A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.5.0", + "@types/pg": "8.11.11", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/@types/pg": { + "version": "8.11.11", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz", + "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/pg-types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz", + "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "license": "MIT", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@prisma/client": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.5.0.tgz", + "integrity": "sha512-h4hF9ctp+kSRs7ENHGsFQmHAgHcfkOCxbYt6Ti9Xi8x7D+kP4tTi9x51UKmiTH/OqdyJAO+8V+r+JA5AWdav7w==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.5.0" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.5.0.tgz", + "integrity": "sha512-KnJ2b4Si/pcWEtK68uM+h0h1oh80CZt2suhLTVuLaSKg4n58Q9jBF/A42Kw6Ma+aThy1yAhfDeTC0JvEmeZnFQ==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.5.0.tgz", + "integrity": "sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.5.0.tgz", + "integrity": "sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz", + "integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.3.15", + "@electric-sql/pglite-socket": "0.0.20", + "@electric-sql/pglite-tools": "0.2.20", + "@hono/node-server": "1.19.9", + "@mrleebo/prisma-ast": "0.13.1", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "foreground-child": "3.3.1", + "get-port-please": "3.2.0", + "hono": "4.11.4", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.33.4", + "std-env": "3.10.0", + "valibot": "1.2.0", + "zeptomatch": "2.1.0" + } + }, + "node_modules/@prisma/dev/node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@prisma/dev/node_modules/hono": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.5.0.tgz", + "integrity": "sha512-B79N/amgV677mFesFDBAdrW0OIaqawap9E0sjgLBtzIz2R3hIMS1QB8mLZuUEiS4q5Y8Oh3I25Kw4SLxMypk9Q==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.5.0" + } + }, + "node_modules/@prisma/engines": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.5.0.tgz", + "integrity": "sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/fetch-engine": "7.5.0", + "@prisma/get-platform": "7.5.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e.tgz", + "integrity": "sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.5.0" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.5.0.tgz", + "integrity": "sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.5.0", + "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e", + "@prisma/get-platform": "7.5.0" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz", + "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.5.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz", + "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz", + "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz", + "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/studio-core": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.21.1.tgz", + "integrity": "sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19 || ^22.12 || ^24.0", + "pnpm": "8" + }, + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1947,6 +2792,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2313,6 +3165,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2338,12 +3197,23 @@ "version": "20.19.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -3289,6 +4159,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/axe-core": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", @@ -3328,6 +4208,15 @@ "node": ">=6.0.0" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -3432,6 +4321,48 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3526,6 +4457,47 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3701,6 +4673,23 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -3940,6 +4929,16 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/default-browser": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", @@ -4016,6 +5015,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4025,6 +5041,13 @@ "node": ">= 0.8" } }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -4106,6 +5129,17 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.313", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", @@ -4119,6 +5153,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -4335,6 +5379,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4923,6 +6009,36 @@ "express": ">= 4.11" } }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5136,6 +6252,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -5180,6 +6313,21 @@ "node": ">=14.14" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5226,6 +6374,16 @@ "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", "license": "MIT" }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -5302,6 +6460,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-port-please": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz", + "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==", + "devOptional": true, + "license": "MIT" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -5362,6 +6527,24 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -5423,6 +6606,20 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/grammex": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", + "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/graphmatch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz", + "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/graphql": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.1.tgz", @@ -5576,6 +6773,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -6063,6 +7267,13 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -6294,7 +7505,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -6726,6 +7937,16 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6748,6 +7969,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6795,6 +8023,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6817,6 +8052,22 @@ "yallist": "^3.0.2" } }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "devOptional": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/lucide-react": { "version": "0.577.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", @@ -7034,6 +8285,40 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -7137,6 +8422,43 @@ } } }, + "node_modules/next-auth": { + "version": "5.0.0-beta.30", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.30.tgz", + "integrity": "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.0" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", + "nodemailer": "^7.0.7", + "react": "^18.2.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -7222,6 +8544,13 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.36", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", @@ -7256,6 +8585,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nypm": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.0", + "pathe": "^2.0.3", + "tinyexec": "^1.0.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz", + "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/oauth4webapi": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7386,6 +8749,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "license": "MIT" + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -7640,6 +9016,127 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7667,6 +9164,18 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -7718,6 +9227,65 @@ "node": ">=4" } }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "devOptional": true, + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "license": "MIT" + }, "node_modules/powershell-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", @@ -7730,6 +9298,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/preact": { + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7755,6 +9342,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prisma": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.5.0.tgz", + "integrity": "sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "7.5.0", + "@prisma/dev": "0.20.0", + "@prisma/engines": "7.5.0", + "@prisma/studio-core": "0.21.1", + "mysql2": "3.15.3", + "postgres": "3.4.7" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24.0" + }, + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -7789,6 +9410,25 @@ "react-is": "^16.13.1" } }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -7812,6 +9452,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", @@ -7871,6 +9528,17 @@ "node": ">= 0.10" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -7899,6 +9567,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", @@ -7938,6 +9620,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -7959,6 +9648,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remeda": { + "version": "2.33.4", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", + "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/remeda" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -8039,6 +9738,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rettime": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz", @@ -8218,6 +9927,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true + }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -8557,6 +10272,16 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8575,6 +10300,25 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -8591,6 +10335,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/stdin-discarder": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", @@ -8929,6 +10680,16 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -9083,6 +10844,26 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", @@ -9273,7 +11054,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, "license": "MIT" }, "node_modules/unicorn-magic": { @@ -9405,6 +11185,21 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/valibot": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", + "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/validate-npm-package-name": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", @@ -9623,6 +11418,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -9743,6 +11547,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zeptomatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", + "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.11", + "graphmatch": "^1.1.0" + } + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/package.json b/package.json index 86fedab..9f6c21c 100644 --- a/package.json +++ b/package.json @@ -6,28 +6,48 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "db:migrate": "npx prisma migrate dev", + "db:push": "npx prisma db push", + "db:seed": "npx tsx prisma/seed.ts", + "db:studio": "npx prisma studio", + "postinstall": "npx prisma generate" + }, + "prisma": { + "seed": "npx tsx prisma/seed.ts" }, "dependencies": { "@base-ui/react": "^1.3.0", + "@prisma/adapter-pg": "^7.5.0", + "@prisma/client": "^7.5.0", + "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.577.0", "next": "16.1.7", + "next-auth": "^5.0.0-beta.30", + "next-themes": "^0.4.6", + "pg": "^8.20.0", "react": "19.2.3", "react-dom": "19.2.3", "shadcn": "^4.0.8", + "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.4.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^2.4.6", "@types/node": "^20", + "@types/pg": "^8.18.0", "@types/react": "^19", "@types/react-dom": "^19", + "dotenv": "^17.3.1", "eslint": "^9", "eslint-config-next": "16.1.7", + "prisma": "^7.5.0", "tailwindcss": "^4", + "tsx": "^4.21.0", "typescript": "^5" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..831a20f --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma, and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: process.env["DATABASE_URL"], + }, +}); diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..a67761c --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,89 @@ +generator client { + provider = "prisma-client" + output = "../src/generated/prisma" +} + +datasource db { + provider = "postgresql" +} + +model Admin { + id String @id @default(cuid()) + username String @unique + passwordHash String + createdAt DateTime @default(now()) +} + +model Addon { + id String @id @default(cuid()) + name String + slug String @unique + summary String + description String @db.Text + iconUrl String? + category String @default("general") + published Boolean @default(false) + totalDownloads Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + releases Release[] + screenshots Screenshot[] +} + +model Release { + id String @id @default(cuid()) + addonId String + version String + changelog String @db.Text + downloadType String @default("local") + filePath String? + externalUrl String? + gameVersion String @default("") + downloadCount Int @default(0) + isLatest Boolean @default(false) + createdAt DateTime @default(now()) + addon Addon @relation(fields: [addonId], references: [id], onDelete: Cascade) + + @@index([addonId]) +} + +model Screenshot { + id String @id @default(cuid()) + addonId String + imageUrl String + sortOrder Int @default(0) + addon Addon @relation(fields: [addonId], references: [id], onDelete: Cascade) + + @@index([addonId]) +} + +model Software { + id String @id @default(cuid()) + name String + slug String @unique + description String @default("") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + versions SoftwareVersion[] +} + +model SoftwareVersion { + id String @id @default(cuid()) + softwareId String + version String + versionCode Int + changelog String @db.Text + downloadType String @default("local") + filePath String? + externalUrl String? + fileSize Int @default(0) + downloadCount Int @default(0) + isLatest Boolean @default(false) + forceUpdate Boolean @default(false) + minVersion String? + createdAt DateTime @default(now()) + software Software @relation(fields: [softwareId], references: [id], onDelete: Cascade) + + @@unique([softwareId, version]) + @@index([softwareId]) +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..ba4f07c --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,48 @@ +import "dotenv/config"; +import { PrismaClient } from "../src/generated/prisma/client"; +import { PrismaPg } from "@prisma/adapter-pg"; +import bcrypt from "bcryptjs"; + +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! }); +const prisma = new PrismaClient({ adapter }); + +async function main() { + const username = process.env.ADMIN_USERNAME || "admin"; + const password = process.env.ADMIN_PASSWORD || "admin123"; + const passwordHash = await bcrypt.hash(password, 12); + + await prisma.admin.upsert({ + where: { username }, + update: { passwordHash }, + create: { username, passwordHash }, + }); + + console.log(`Admin user "${username}" created/updated.`); + + const existingAddon = await prisma.addon.findUnique({ + where: { slug: "nanami" }, + }); + + if (!existingAddon) { + await prisma.addon.create({ + data: { + name: "Nanami", + slug: "nanami", + summary: "A powerful WoW addon that enhances your gameplay experience.", + description: + "# Nanami\n\nNanami is a comprehensive World of Warcraft addon designed to improve your gaming experience.\n\n## Features\n\n- Feature 1\n- Feature 2\n- Feature 3", + category: "gameplay", + published: true, + }, + }); + console.log("Sample addon 'Nanami' created."); + } +} + +main() + .then(() => prisma.$disconnect()) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/public/banners/banner_1.png b/public/banners/banner_1.png new file mode 100644 index 0000000..54e0013 Binary files /dev/null and b/public/banners/banner_1.png differ diff --git a/public/banners/banner_2.png b/public/banners/banner_2.png new file mode 100644 index 0000000..e472c3a Binary files /dev/null and b/public/banners/banner_2.png differ diff --git a/public/banners/banner_3.png b/public/banners/banner_3.png new file mode 100644 index 0000000..2415731 Binary files /dev/null and b/public/banners/banner_3.png differ diff --git a/src/app/(public)/addons/[slug]/page.tsx b/src/app/(public)/addons/[slug]/page.tsx new file mode 100644 index 0000000..7b138a8 --- /dev/null +++ b/src/app/(public)/addons/[slug]/page.tsx @@ -0,0 +1,209 @@ +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/db"; +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Download, Package, Calendar, Tag } from "lucide-react"; +import { DownloadButton } from "@/components/public/DownloadButton"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const addon = await prisma.addon.findUnique({ + where: { slug }, + select: { name: true, summary: true }, + }); + if (!addon) return { title: "Not Found" }; + return { + title: `${addon.name} - Nanami`, + description: addon.summary, + }; +} + +export const dynamic = "force-dynamic"; + +export default async function AddonDetailPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const { slug } = await params; + const addon = await prisma.addon.findUnique({ + where: { slug }, + include: { + releases: { orderBy: { createdAt: "desc" } }, + screenshots: { orderBy: { sortOrder: "asc" } }, + }, + }); + + if (!addon || !addon.published) notFound(); + + const latestRelease = addon.releases.find((r) => r.isLatest); + + return ( +
+ {/* Header */} +
+ {addon.iconUrl ? ( + {addon.name} + ) : ( +
+ +
+ )} +
+

{addon.name}

+

{addon.summary}

+
+ {addon.category} + + + {addon.totalDownloads.toLocaleString()} 次下载 + + {latestRelease && ( + + + v{latestRelease.version} + + )} +
+
+ {latestRelease && ( +
+ +
+ )} +
+ + + +
+ {/* Description */} +
+ + + 介绍 + + +
+ +
+
+
+ + {/* Screenshots */} + {addon.screenshots.length > 0 && ( + + + 截图 + + +
+ {addon.screenshots.map((ss) => ( + Screenshot + ))} +
+
+
+ )} +
+ + {/* Sidebar - Releases */} +
+ + + 版本历史 + + 共 {addon.releases.length} 个版本 + + + + {addon.releases.map((release) => ( +
+
+
+ v{release.version} + {release.isLatest && ( + + 最新 + + )} +
+ +
+ {release.gameVersion && ( +

+ WoW {release.gameVersion} +

+ )} +
+ + + {new Date(release.createdAt).toLocaleDateString("zh-CN")} + + + + {release.downloadCount} + +
+ {release.changelog && ( +

+ {release.changelog} +

+ )} +
+ ))} + {addon.releases.length === 0 && ( +

暂无版本发布

+ )} +
+
+
+
+
+ ); +} + +function MarkdownContent({ content }: { content: string }) { + const html = content + .replace(/^### (.*$)/gm, '

$1

') + .replace(/^## (.*$)/gm, '

$1

') + .replace(/^# (.*$)/gm, '

$1

') + .replace(/\*\*(.*?)\*\*/g, "$1") + .replace(/\*(.*?)\*/g, "$1") + .replace(/`(.*?)`/g, '$1') + .replace(/^- (.*$)/gm, '
  • $1
  • ') + .replace(/\n\n/g, '

    ') + .replace(/\n/g, "
    "); + + return
    ; +} diff --git a/src/app/(public)/addons/page.tsx b/src/app/(public)/addons/page.tsx new file mode 100644 index 0000000..090eeb7 --- /dev/null +++ b/src/app/(public)/addons/page.tsx @@ -0,0 +1,101 @@ +import { prisma } from "@/lib/db"; +import { AddonCard } from "@/components/public/AddonCard"; +import { Badge } from "@/components/ui/badge"; +import Link from "next/link"; + +const categoryLabels: Record = { + general: "通用", + gameplay: "游戏玩法", + ui: "界面增强", + combat: "战斗", + raid: "团队副本", + pvp: "PvP", + tradeskill: "专业技能", + utility: "实用工具", +}; + +export const metadata = { + title: "插件列表 - Nanami", +}; + +export const dynamic = "force-dynamic"; + +export default async function AddonsPage({ + searchParams, +}: { + searchParams: Promise<{ category?: string; search?: string }>; +}) { + const { category, search } = await searchParams; + + const where: Record = { published: true }; + if (category) where.category = category; + if (search) { + where.OR = [ + { name: { contains: search, mode: "insensitive" } }, + { summary: { contains: search, mode: "insensitive" } }, + ]; + } + + const addons = await prisma.addon.findMany({ + where, + include: { + releases: { + where: { isLatest: true }, + select: { version: true }, + }, + }, + orderBy: { totalDownloads: "desc" }, + }); + + const categories = await prisma.addon.groupBy({ + by: ["category"], + where: { published: true }, + _count: { id: true }, + }); + + return ( +
    +

    插件列表

    +

    + 浏览和下载 World of Warcraft 插件 +

    + + {/* Category Filter */} +
    + + + 全部 + + + {categories.map((cat) => ( + + + {categoryLabels[cat.category] || cat.category} ({cat._count.id}) + + + ))} +
    + + {/* Addon Grid */} +
    + {addons.map((addon) => ( + + ))} +
    + + {addons.length === 0 && ( +
    +

    + {search ? `没有找到"${search}"相关的插件` : "暂无插件"} +

    +
    + )} +
    + ); +} diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx new file mode 100644 index 0000000..30d0dac --- /dev/null +++ b/src/app/(public)/layout.tsx @@ -0,0 +1,16 @@ +import { Navbar } from "@/components/public/Navbar"; +import { Footer } from "@/components/public/Footer"; + +export default function PublicLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
    + +
    {children}
    +
    +
    + ); +} diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx new file mode 100644 index 0000000..46aeb2b --- /dev/null +++ b/src/app/(public)/page.tsx @@ -0,0 +1,110 @@ +import Link from "next/link"; +import { prisma } from "@/lib/db"; +import { Button } from "@/components/ui/button"; +import { AddonCard } from "@/components/public/AddonCard"; +import { HeroBanner } from "@/components/public/HeroBanner"; +import { Sparkles, Shield, Zap } from "lucide-react"; + +export const dynamic = "force-dynamic"; + +export default async function HomePage() { + const [featuredAddons, totalDownloads, launcher] = await Promise.all([ + prisma.addon.findMany({ + where: { published: true }, + include: { + releases: { + where: { isLatest: true }, + select: { version: true }, + }, + }, + orderBy: { totalDownloads: "desc" }, + take: 6, + }), + prisma.addon.aggregate({ + _sum: { totalDownloads: true }, + }), + prisma.software.findUnique({ + where: { slug: "nanami-launcher" }, + include: { + versions: { + where: { isLatest: true }, + take: 1, + }, + }, + }), + ]); + + const launcherVersion = launcher?.versions[0]?.version ?? null; + + return ( + <> + + + {/* Features */} +
    +
    +
    +
    +
    +
    + +
    +

    深度适配

    +

    + 专为乌龟服 1.18.0 打造,兼容自定义内容与新种族,稳定流畅 +

    +
    +
    +
    + +
    +

    + 一键安装管理 +

    +

    + 通过 Nanami 启动器自动安装、更新,告别手动拖拽文件夹 +

    +
    +
    +
    + +
    +

    + 内置 AI 翻译 +

    +

    + 自带智能翻译引擎,轻松畅玩英文服务器,语言不再是障碍 +

    +
    +
    +
    +
    + + {/* Featured Addons */} + {featuredAddons.length > 0 && ( +
    +
    +
    +

    热门插件

    + +
    +
    + {featuredAddons.map((addon) => ( + + ))} +
    +
    +
    + )} + + ); +} diff --git a/src/app/admin/(dashboard)/addons/[id]/edit/page.tsx b/src/app/admin/(dashboard)/addons/[id]/edit/page.tsx new file mode 100644 index 0000000..2178fa3 --- /dev/null +++ b/src/app/admin/(dashboard)/addons/[id]/edit/page.tsx @@ -0,0 +1,23 @@ +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/db"; +import { AddonForm } from "@/components/admin/AddonForm"; + +export const dynamic = "force-dynamic"; + +export default async function EditAddonPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const addon = await prisma.addon.findUnique({ where: { id } }); + + if (!addon) notFound(); + + return ( +
    +

    编辑插件

    + +
    + ); +} diff --git a/src/app/admin/(dashboard)/addons/new/page.tsx b/src/app/admin/(dashboard)/addons/new/page.tsx new file mode 100644 index 0000000..3a0878a --- /dev/null +++ b/src/app/admin/(dashboard)/addons/new/page.tsx @@ -0,0 +1,10 @@ +import { AddonForm } from "@/components/admin/AddonForm"; + +export default function NewAddonPage() { + return ( +
    +

    新建插件

    + +
    + ); +} diff --git a/src/app/admin/(dashboard)/addons/page.tsx b/src/app/admin/(dashboard)/addons/page.tsx new file mode 100644 index 0000000..a323ddf --- /dev/null +++ b/src/app/admin/(dashboard)/addons/page.tsx @@ -0,0 +1,87 @@ +import Link from "next/link"; +import { prisma } from "@/lib/db"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Plus } from "lucide-react"; +import { DeleteAddonButton } from "@/components/admin/DeleteAddonButton"; + +export const dynamic = "force-dynamic"; + +export default async function AdminAddonsPage() { + const addons = await prisma.addon.findMany({ + include: { _count: { select: { releases: true } } }, + orderBy: { updatedAt: "desc" }, + }); + + return ( +
    +
    +

    插件管理

    + +
    + +
    + + + + 名称 + Slug + 分类 + 状态 + 版本数 + 下载量 + 操作 + + + + {addons.length === 0 ? ( + + + 暂无插件,点击上方按钮创建 + + + ) : ( + addons.map((addon) => ( + + {addon.name} + + {addon.slug} + + + {addon.category} + + + + {addon.published ? "已发布" : "草稿"} + + + {addon._count.releases} + {addon.totalDownloads} + +
    + + +
    +
    +
    + )) + )} +
    +
    +
    +
    + ); +} diff --git a/src/app/admin/(dashboard)/layout.tsx b/src/app/admin/(dashboard)/layout.tsx new file mode 100644 index 0000000..f563122 --- /dev/null +++ b/src/app/admin/(dashboard)/layout.tsx @@ -0,0 +1,14 @@ +import { Sidebar } from "@/components/admin/Sidebar"; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
    + +
    {children}
    +
    + ); +} diff --git a/src/app/admin/(dashboard)/page.tsx b/src/app/admin/(dashboard)/page.tsx new file mode 100644 index 0000000..4a731b8 --- /dev/null +++ b/src/app/admin/(dashboard)/page.tsx @@ -0,0 +1,99 @@ +import { prisma } from "@/lib/db"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Package, Download, FileUp } from "lucide-react"; + +export const dynamic = "force-dynamic"; + +export default async function DashboardPage() { + const [addonCount, totalDownloads, releaseCount, recentReleases] = + await Promise.all([ + prisma.addon.count(), + prisma.addon.aggregate({ _sum: { totalDownloads: true } }), + prisma.release.count(), + prisma.release.findMany({ + take: 5, + orderBy: { createdAt: "desc" }, + include: { addon: { select: { name: true } } }, + }), + ]); + + const stats = [ + { + title: "插件总数", + value: addonCount, + icon: Package, + }, + { + title: "总下载量", + value: totalDownloads._sum.totalDownloads || 0, + icon: Download, + }, + { + title: "版本发布数", + value: releaseCount, + icon: FileUp, + }, + ]; + + return ( +
    +

    仪表盘

    + +
    + {stats.map((stat) => ( + + + + {stat.title} + + + + +
    {stat.value}
    +
    +
    + ))} +
    + + + + 最近发布 + 最近发布的版本更新 + + + {recentReleases.length === 0 ? ( +

    暂无发布记录

    + ) : ( +
    + {recentReleases.map((release) => ( +
    +
    +

    {release.addon.name}

    +

    + v{release.version} + {release.gameVersion && + ` · WoW ${release.gameVersion}`} +

    +
    +
    +

    {release.downloadCount} 次下载

    +

    {new Date(release.createdAt).toLocaleDateString("zh-CN")}

    +
    +
    + ))} +
    + )} +
    +
    +
    + ); +} diff --git a/src/app/admin/(dashboard)/releases/new/page.tsx b/src/app/admin/(dashboard)/releases/new/page.tsx new file mode 100644 index 0000000..3284b0b --- /dev/null +++ b/src/app/admin/(dashboard)/releases/new/page.tsx @@ -0,0 +1,24 @@ +import { prisma } from "@/lib/db"; +import { ReleaseForm } from "@/components/admin/ReleaseForm"; + +export const dynamic = "force-dynamic"; + +export default async function NewReleasePage() { + const addons = await prisma.addon.findMany({ + select: { id: true, name: true }, + orderBy: { name: "asc" }, + }); + + return ( +
    +

    发布新版本

    + {addons.length === 0 ? ( +

    + 请先创建一个插件后再发布版本。 +

    + ) : ( + + )} +
    + ); +} diff --git a/src/app/admin/(dashboard)/releases/page.tsx b/src/app/admin/(dashboard)/releases/page.tsx new file mode 100644 index 0000000..82bb6ec --- /dev/null +++ b/src/app/admin/(dashboard)/releases/page.tsx @@ -0,0 +1,88 @@ +import Link from "next/link"; +import { prisma } from "@/lib/db"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Plus } from "lucide-react"; + +export const dynamic = "force-dynamic"; + +export default async function AdminReleasesPage() { + const releases = await prisma.release.findMany({ + include: { addon: { select: { name: true, slug: true } } }, + orderBy: { createdAt: "desc" }, + }); + + return ( +
    +
    +

    版本管理

    + +
    + +
    + + + + 插件 + 版本 + 游戏版本 + 下载方式 + 下载量 + 发布时间 + 状态 + + + + {releases.length === 0 ? ( + + + 暂无版本发布 + + + ) : ( + releases.map((release) => ( + + + {release.addon.name} + + v{release.version} + + {release.gameVersion || "-"} + + + + {release.downloadType === "local" ? "本地文件" : "外部链接"} + + + {release.downloadCount} + + {new Date(release.createdAt).toLocaleDateString("zh-CN")} + + + {release.isLatest && ( + 最新 + )} + + + )) + )} + +
    +
    +
    + ); +} diff --git a/src/app/admin/(dashboard)/settings/page.tsx b/src/app/admin/(dashboard)/settings/page.tsx new file mode 100644 index 0000000..9464f3c --- /dev/null +++ b/src/app/admin/(dashboard)/settings/page.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { toast } from "sonner"; + +export default function SettingsPage() { + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + + const formData = new FormData(e.currentTarget); + const newPassword = formData.get("newPassword") as string; + const confirmPassword = formData.get("confirmPassword") as string; + + if (newPassword !== confirmPassword) { + toast.error("两次输入的新密码不一致"); + setLoading(false); + return; + } + + const res = await fetch("/api/admin/change-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + currentPassword: formData.get("currentPassword"), + newPassword, + }), + }); + + const data = await res.json(); + if (res.ok) { + toast.success("密码修改成功"); + (e.target as HTMLFormElement).reset(); + } else { + toast.error(data.error || "修改失败"); + } + + setLoading(false); + } + + return ( +
    +

    系统设置

    + + + 修改密码 + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    +
    +
    + ); +} diff --git a/src/app/admin/(dashboard)/software/[id]/edit/page.tsx b/src/app/admin/(dashboard)/software/[id]/edit/page.tsx new file mode 100644 index 0000000..6dd6257 --- /dev/null +++ b/src/app/admin/(dashboard)/software/[id]/edit/page.tsx @@ -0,0 +1,26 @@ +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/db"; +import { SoftwareEditForm } from "@/components/admin/SoftwareEditForm"; + +export const dynamic = "force-dynamic"; + +export default async function EditSoftwarePage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const software = await prisma.software.findUnique({ + where: { id }, + include: { versions: { orderBy: { versionCode: "desc" } } }, + }); + + if (!software) notFound(); + + return ( +
    +

    编辑软件 - {software.name}

    + +
    + ); +} diff --git a/src/app/admin/(dashboard)/software/[id]/versions/new/page.tsx b/src/app/admin/(dashboard)/software/[id]/versions/new/page.tsx new file mode 100644 index 0000000..9b83f32 --- /dev/null +++ b/src/app/admin/(dashboard)/software/[id]/versions/new/page.tsx @@ -0,0 +1,28 @@ +import { notFound } from "next/navigation"; +import { prisma } from "@/lib/db"; +import { SoftwareVersionForm } from "@/components/admin/SoftwareVersionForm"; + +export const dynamic = "force-dynamic"; + +export default async function NewSoftwareVersionPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const software = await prisma.software.findUnique({ + where: { id }, + select: { id: true, name: true, slug: true }, + }); + + if (!software) notFound(); + + return ( +
    +

    + 发布新版本 - {software.name} +

    + +
    + ); +} diff --git a/src/app/admin/(dashboard)/software/new/page.tsx b/src/app/admin/(dashboard)/software/new/page.tsx new file mode 100644 index 0000000..80a5634 --- /dev/null +++ b/src/app/admin/(dashboard)/software/new/page.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { toast } from "sonner"; + +export default function NewSoftwarePage() { + const router = useRouter(); + const [loading, setLoading] = useState(false); + + function generateSlug(name: string) { + return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + + const fd = new FormData(e.currentTarget); + const res = await fetch("/api/software", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: fd.get("name"), + slug: fd.get("slug"), + description: fd.get("description"), + }), + }); + + if (res.ok) { + toast.success("创建成功"); + router.push("/admin/software"); + router.refresh(); + } else { + const err = await res.json(); + toast.error(err.error || "创建失败"); + } + setLoading(false); + } + + return ( +
    +

    新建软件

    + + 软件信息 + +
    +
    + + { + const slugInput = document.getElementById("slug") as HTMLInputElement; + if (slugInput) slugInput.value = generateSlug(e.target.value); + }} + /> +
    +
    + + +
    +
    + +