From 557ecebee67df91b67b5f09cb38c34c13237a1d7 Mon Sep 17 00:00:00 2001 From: rucky Date: Fri, 15 May 2026 19:17:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B9=8C=E9=BE=9F=E6=9C=8D=E5=AE=98=E6=9C=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API.md | 83 +++++-------------- prisma/schema.prisma | 4 +- src/app/(public)/addons/[slug]/page.tsx | 4 +- src/app/(public)/addons/page.tsx | 4 +- src/app/(public)/changelog/page.tsx | 4 +- src/app/(public)/page.tsx | 4 +- src/app/admin/(dashboard)/releases/page.tsx | 2 + .../(dashboard)/software/[id]/edit/page.tsx | 8 +- src/app/admin/(dashboard)/software/page.tsx | 46 ++++------ src/app/api/addons/[id]/route.ts | 6 +- src/app/api/addons/route.ts | 5 +- src/app/api/download/[id]/route.ts | 5 +- src/app/api/releases/[id]/route.ts | 13 +-- src/app/api/releases/route.ts | 12 +-- src/app/api/software/[id]/route.ts | 6 +- src/app/api/software/[id]/versions/route.ts | 5 +- src/app/api/software/changelog/route.ts | 6 +- src/app/api/software/check-update/route.ts | 6 +- src/app/api/software/download/[id]/route.ts | 5 +- .../software/versions/[versionId]/route.ts | 13 +-- src/app/layout.tsx | 17 ++-- src/components/admin/ReleaseForm.tsx | 26 +----- src/components/admin/ReleasesTable.tsx | 35 +------- src/components/admin/SoftwareEditForm.tsx | 27 +----- src/components/admin/SoftwareVersionForm.tsx | 28 +------ src/components/admin/SoftwareVersionTable.tsx | 34 +------- src/components/public/Footer.tsx | 4 +- src/components/public/HeroBanner.tsx | 4 +- src/components/public/Navbar.tsx | 3 - src/components/public/WowVersionSwitcher.tsx | 62 -------------- src/i18n/WowVersionProvider.tsx | 68 --------------- src/lib/get-server-wow.ts | 18 ---- src/lib/wow-versions.ts | 55 +++--------- 33 files changed, 118 insertions(+), 504 deletions(-) delete mode 100644 src/components/public/WowVersionSwitcher.tsx delete mode 100644 src/i18n/WowVersionProvider.tsx delete mode 100644 src/lib/get-server-wow.ts diff --git a/API.md b/API.md index 0ddadce..44da1f0 100644 --- a/API.md +++ b/API.md @@ -11,57 +11,27 @@ Bilingual (zh / en) public REST API for the Nanami Turtle WoW platform. --- -## 🎮 WoW 客户端版本(多版本支持) +## 🎮 WoW 客户端版本 -平台为 Turtle WoW **1.18** 与 **1.17** 两套客户端独立维护启动器、插件版本与发布渠道。每个 `Release` / `SoftwareVersion` 都打了 `wowVersion` 标签,且 `isLatest` 在 `(addon|software, wowVersion)` 维度内唯一 —— 同一插件可以同时存在「1.18 最新版」和「1.17 最新版」两条 latest。 +平台当前只对外提供 Turtle WoW **1.18** 版本。每个 `Release` / `SoftwareVersion` 仍保留 `wowVersion` 字段用于标识历史数据和数据库约束,但公共网页、后台、下载和更新接口都会固定使用 `1.18`。 -### 如何选择 wow 版本 +### 版本解析 -不同接口的解析策略略有差异: - -#### 列表 / 浏览类接口(addons / releases / software / changelog) - -| 来源 | 示例 | 优先级 | -|------|------|--------| -| 查询参数 `wow` | `?wow=1.18` 或 `?wow=1.17` | 1(最高) | -| 查询参数 `wowVersion` | `?wowVersion=1.17` | 2(别名) | -| Cookie `wow` | 浏览器侧由前台切换设置 | 3 | -| 默认 | — | `1.18` | - -这些接口让浏览器在导航过程中保留用户选择的 wow 频道(cookie 持久化)。多数列表型接口还支持 `?wow=all` 表示不过滤、返回所有版本。 - -#### 下载 / 自更新类接口(latest / check-update / download/launcher) - -| 来源 | 示例 | 优先级 | -|------|------|--------| -| 查询参数 `wow` | `?wow=1.18` | 1(最高) | -| 查询参数 `wowVersion` | `?wowVersion=1.17` | 2(别名) | -| 默认 | — | `1.18` | - -⚠️ **下载类接口故意不读 Cookie**。这是一个强约定:**URL 自己完全决定下载到的二进制**。 - -为什么这样设计: -1. `https://nanami.rucky.cn/download/launcher` 这种第三方嵌入直链,必须永远稳定指向 1.18 版本,不能被访客之前在前台切换过的 cookie 污染。 -2. 启动器自更新(`/api/software/check-update`)的频道由启动器自身声明(`?wow=1.17`),避免 1.18 客户端因为 cookie 串台拿到 1.17 的更新。 -3. 不论中英文(`lang`)切换,下载到的二进制都是同一个,只跟 `?wow=` 相关。 - -合法值:`1.18`、`1.17`。 +- `wow` / `wowVersion` 查询参数可以省略;即使传入其他历史值,也会回落到 `1.18` +- 浏览器 Cookie 不再参与 wow 版本选择 +- 列表接口不再支持 `?wow=all` 返回所有历史版本 ### 响应字段 -- 单条对象上含 `wowVersion`,标识该 release / version 所属的客户端版本 -- 列表型响应顶层含 `wowVersion` 字段,回显本次过滤值(`"all"` 表示未过滤) +- 单条对象上含 `wowVersion`,当前对外返回值为 `"1.18"` +- 列表型响应顶层含 `wowVersion` 字段,回显当前固定过滤值 ### 启动器自更新建议 -启动器调用 `/api/software/check-update` 时务必带上 `wow=1.18` 或 `wow=1.17`,否则会按默认 `1.18` 返回 —— 1.17 客户端拿到 1.18 的更新就出问题了。 +启动器调用 `/api/software/check-update` 时无需传 wow 频道;服务端始终返回 1.18 通道。 ```bash -# 1.17 启动器自检更新(已安装 versionCode=1010) -curl 'https://nanami.rucky.cn/api/software/check-update?slug=nanami-launcher&versionCode=1010&wow=1.17' - -# 1.18 启动器 -curl 'https://nanami.rucky.cn/api/software/check-update?slug=nanami-launcher&versionCode=1010&wow=1.18' +curl 'https://nanami.rucky.cn/api/software/check-update?slug=nanami-launcher&versionCode=1010' ``` --- @@ -114,7 +84,7 @@ GET /api/software/check-update?slug={slug}&versionCode={n}&wow={wow}&lang={lang} |------|------|------|------| | slug | string | ✅ | 软件标识,例如 `nanami-launcher`、`nanami-launcher-patch` | | versionCode | number | ❌ | 当前客户端版本号(整数),缺省为 0 | -| wow | string | ❌ | `1.18` / `1.17`,缺省 `1.18` | +| wow | string | ❌ | 固定为 `1.18`;可省略 | | lang | string | ❌ | `zh` / `en`,缺省 `zh` | **响应示例(wow=1.18, lang=en):** @@ -140,7 +110,7 @@ GET /api/software/check-update?slug={slug}&versionCode={n}&wow={wow}&lang={lang} } ``` -`forceUpdate=true` 仅当存在更新且最新版被标记为强制更新。`isLatest` 是 `(software, wowVersion)` 维度的 —— 1.18 与 1.17 各自有独立的 latest,互不影响。下载 URL 自动带 `source=launcher`,启动器更新下载会单独计数。 +`forceUpdate=true` 仅当存在更新且最新版被标记为强制更新。下载 URL 自动带 `source=launcher`,启动器更新下载会单独计数。 --- @@ -206,14 +176,13 @@ GET /download/launcher?wow={wow} | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| -| wow | string | ❌ | `1.18` / `1.17`,缺省 `1.18` | +| wow | string | ❌ | 固定为 `1.18`;可省略 | -⚠️ **第三方链接安全**:这条 URL 是公开嵌入用的,**只看显式 `?wow=` 参数**,**不读用户 cookie**。意思是:即使用户之前在前台切到 1.17,再点这条不带 `?wow=` 的链接仍然会下载到 1.18。这样可以让站外的「下载启动器」按钮稳定指向 1.18。 +⚠️ **第三方链接安全**:这条 URL 是公开嵌入用的,不读用户 cookie,始终下载 1.18 通道。 ```html 下载 Nanami 启动器(默认 WoW 1.18) 下载 Nanami 启动器(WoW 1.18) -下载 Nanami 启动器(WoW 1.17) ``` --- @@ -258,7 +227,7 @@ GET /api/software/changelog?slug={slug}&wow={wow}&lang={lang} | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | slug | string | ✅ | 软件标识 | -| wow | string | ❌ | `1.18` / `1.17`(缺省 `1.18`),传 `all` 不过滤 | +| wow | string | ❌ | 固定为 `1.18`;可省略 | | lang | string | ❌ | `zh` / `en` | **响应示例(wow=1.18, lang=en):** @@ -331,7 +300,7 @@ GET /api/software/changelog?slug={slug}&wow={wow}&lang={lang} | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | lang | string | ❌ | `zh` / `en` | -| wow | string | ❌ | `1.18` / `1.17` —— 仅返回此 WoW 版本下的 latest release;传 `all` 表示不过滤 | +| wow | string | ❌ | 固定为 `1.18`;可省略 | | published | string | ❌ | 默认 `true`;传 `false` 包括草稿 | | category | string | ❌ | 按分类过滤 | | search | string | ❌ | 名称 / 简介模糊匹配,中英都搜 | @@ -390,7 +359,7 @@ GET /api/software/changelog?slug={slug}&wow={wow}&lang={lang} | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | addonId | string | ❌ | 限定某个插件;缺省返回全平台所有插件的 release | -| wow | string | ❌ | `1.18` / `1.17`,缺省 `1.18`;传 `all` 不过滤 | +| wow | string | ❌ | 固定为 `1.18`;可省略 | | lang | string | ❌ | `zh` / `en` | **响应示例:** @@ -538,7 +507,7 @@ GET /api/server-time | GET | `/api/software/check-update` | ✏️ | ✅ | ✅ | 启动器自更新检查 | | GET | `/api/software/download/{id}` | ✏️ | ❌ | ❌ | 下载启动器文件(按 versionId 精确) | | GET | `/api/software/latest` | ✏️ | ✅ | ✅ | 最新启动器信息 / 下载(网页用) | -| GET | `/download/launcher` | 🆕 | ❌ | ✅ | 友好直链:按 wow 始终下载最新 | +| GET | `/download/launcher` | 🆕 | ❌ | ✅ | 友好直链:始终下载 1.18 最新 | | POST | `/api/launcher/heartbeat` | — | ❌ | ❌ | 上报启动器在线状态 | | GET | `/api/software/changelog` | ✏️ | ✅ | ✅ | 启动器历史 changelog | | GET | `/api/software` | 🆕 | ✅ | ✅ | 软件列表 | @@ -561,27 +530,15 @@ GET /api/server-time # 1.18 启动器最新版(英文 changelog) curl 'https://nanami.rucky.cn/api/software/latest?info=1&lang=en&wow=1.18' -# 1.17 启动器最新版 -curl 'https://nanami.rucky.cn/api/software/latest?info=1&wow=1.17' - # 1.18 启动器全部历史 changelog(中文) curl 'https://nanami.rucky.cn/api/software/changelog?slug=nanami-launcher&wow=1.18&lang=zh' -# 1.17 启动器自更新检查(已安装 versionCode=1010) -curl 'https://nanami.rucky.cn/api/software/check-update?slug=nanami-launcher&versionCode=1010&wow=1.17' - -# 1.17 客户端的 UI 类插件(英文) -curl 'https://nanami.rucky.cn/api/addons?lang=en&category=ui&wow=1.17' - -# 不限 WoW 版本,列出所有 release -curl 'https://nanami.rucky.cn/api/releases?wow=all' +# 1.18 客户端的 UI 类插件(英文) +curl 'https://nanami.rucky.cn/api/addons?lang=en&category=ui' # 1.18 启动器友好直链 curl -L -o nanami-launcher-1.18.exe 'https://nanami.rucky.cn/download/launcher?wow=1.18' -# 1.17 启动器友好直链 -curl -L -o nanami-launcher-1.17.exe 'https://nanami.rucky.cn/download/launcher?wow=1.17' - # 通过 Accept-Language 协商语言 curl -H 'Accept-Language: en-US,en;q=0.9' 'https://nanami.rucky.cn/api/articles' ``` diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c54eae4..618842d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -46,7 +46,7 @@ model Release { filePath String? externalUrl String? gameVersion String @default("") - // Turtle WoW client major version this build targets: "1.18" or "1.17" + // Turtle WoW client major version this build targets. Only "1.18" is active. wowVersion String @default("1.18") downloadCount Int @default(0) isLatest Boolean @default(false) @@ -95,7 +95,7 @@ model SoftwareVersion { isLatest Boolean @default(false) forceUpdate Boolean @default(false) minVersion String? - // Turtle WoW client major version this build targets: "1.18" or "1.17" + // Turtle WoW client major version this build targets. Only "1.18" is active. wowVersion String @default("1.18") createdAt DateTime @default(now()) software Software @relation(fields: [softwareId], references: [id], onDelete: Cascade) diff --git a/src/app/(public)/addons/[slug]/page.tsx b/src/app/(public)/addons/[slug]/page.tsx index 3a5d163..162e005 100644 --- a/src/app/(public)/addons/[slug]/page.tsx +++ b/src/app/(public)/addons/[slug]/page.tsx @@ -1,7 +1,7 @@ import { notFound } from "next/navigation"; import { prisma } from "@/lib/db"; import { AddonDetail } from "@/components/public/AddonDetail"; -import { getServerWowVersion } from "@/lib/get-server-wow"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export const dynamic = "force-dynamic"; @@ -30,7 +30,7 @@ export default async function AddonDetailPage({ params: Promise<{ slug: string }>; }) { const { slug } = await params; - const wowVersion = await getServerWowVersion(); + const wowVersion = DEFAULT_WOW_VERSION; const addon = await prisma.addon.findUnique({ where: { slug }, include: { diff --git a/src/app/(public)/addons/page.tsx b/src/app/(public)/addons/page.tsx index a5b2aa3..41a88ae 100644 --- a/src/app/(public)/addons/page.tsx +++ b/src/app/(public)/addons/page.tsx @@ -2,7 +2,7 @@ import { prisma } from "@/lib/db"; import { AddonCard } from "@/components/public/AddonCard"; import { AddonsCategoryFilter } from "@/components/public/AddonsCategoryFilter"; import { T } from "@/components/public/T"; -import { getServerWowVersion } from "@/lib/get-server-wow"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; import Link from "next/link"; import { Package, Search, ChevronLeft, ChevronRight } from "lucide-react"; @@ -24,7 +24,7 @@ export default async function AddonsPage({ }) { const { category, search, page: pageStr } = await searchParams; const page = Math.max(1, parseInt(pageStr || "1", 10) || 1); - const wowVersion = await getServerWowVersion(); + const wowVersion = DEFAULT_WOW_VERSION; const where: Record = { published: true }; if (category) where.category = category; diff --git a/src/app/(public)/changelog/page.tsx b/src/app/(public)/changelog/page.tsx index 1694946..0c4b441 100644 --- a/src/app/(public)/changelog/page.tsx +++ b/src/app/(public)/changelog/page.tsx @@ -1,6 +1,6 @@ import { prisma } from "@/lib/db"; import { ChangelogTimeline } from "@/components/public/ChangelogTimeline"; -import { getServerWowVersion } from "@/lib/get-server-wow"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export const dynamic = "force-dynamic"; @@ -12,7 +12,7 @@ export const metadata = { export const revalidate = 120; export default async function ChangelogPage() { - const wowVersion = await getServerWowVersion(); + const wowVersion = DEFAULT_WOW_VERSION; const software = await prisma.software.findUnique({ where: { slug: "nanami-launcher" }, include: { diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index ebc78a8..ea5b36e 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -8,14 +8,14 @@ import { ShutdownBanner } from "@/components/public/ShutdownBanner"; import { T } from "@/components/public/T"; import { ArticleCard } from "@/components/public/ArticleCard"; import { getSiteSettings } from "@/lib/site-settings"; -import { getServerWowVersion } from "@/lib/get-server-wow"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; import { Sparkles, Shield, Zap } from "lucide-react"; export const dynamic = "force-dynamic"; export default async function HomePage() { const siteSettings = await getSiteSettings(); - const wowVersion = await getServerWowVersion(); + const wowVersion = DEFAULT_WOW_VERSION; const [featuredAddons, launcher, launcherDownloads, banners, galleryImages, latestArticles] = await Promise.all([ prisma.addon.findMany({ diff --git a/src/app/admin/(dashboard)/releases/page.tsx b/src/app/admin/(dashboard)/releases/page.tsx index 01d2c75..2ab0909 100644 --- a/src/app/admin/(dashboard)/releases/page.tsx +++ b/src/app/admin/(dashboard)/releases/page.tsx @@ -3,11 +3,13 @@ import { prisma } from "@/lib/db"; import { Button } from "@/components/ui/button"; import { Plus } from "lucide-react"; import { ReleasesTable } from "@/components/admin/ReleasesTable"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export const dynamic = "force-dynamic"; export default async function AdminReleasesPage() { const releases = await prisma.release.findMany({ + where: { wowVersion: DEFAULT_WOW_VERSION }, include: { addon: { select: { name: true, slug: true } } }, orderBy: { createdAt: "desc" }, }); diff --git a/src/app/admin/(dashboard)/software/[id]/edit/page.tsx b/src/app/admin/(dashboard)/software/[id]/edit/page.tsx index 6dd6257..6052341 100644 --- a/src/app/admin/(dashboard)/software/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/software/[id]/edit/page.tsx @@ -1,6 +1,7 @@ import { notFound } from "next/navigation"; import { prisma } from "@/lib/db"; import { SoftwareEditForm } from "@/components/admin/SoftwareEditForm"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export const dynamic = "force-dynamic"; @@ -12,7 +13,12 @@ export default async function EditSoftwarePage({ const { id } = await params; const software = await prisma.software.findUnique({ where: { id }, - include: { versions: { orderBy: { versionCode: "desc" } } }, + include: { + versions: { + where: { wowVersion: DEFAULT_WOW_VERSION }, + orderBy: { versionCode: "desc" }, + }, + }, }); if (!software) notFound(); diff --git a/src/app/admin/(dashboard)/software/page.tsx b/src/app/admin/(dashboard)/software/page.tsx index a5b4397..91af9c4 100644 --- a/src/app/admin/(dashboard)/software/page.tsx +++ b/src/app/admin/(dashboard)/software/page.tsx @@ -10,6 +10,7 @@ import { } from "@/components/ui/card"; import { Plus } from "lucide-react"; import { SoftwareVersionTable } from "@/components/admin/SoftwareVersionTable"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; const SOFTWARE_DEFS = [ { @@ -41,7 +42,7 @@ export default async function AdminSoftwarePage() { SOFTWARE_DEFS.map(async (def) => { const sw = await ensureSoftware(def.slug, def.name, def.description); const versions = await prisma.softwareVersion.findMany({ - where: { softwareId: sw.id }, + where: { softwareId: sw.id, wowVersion: DEFAULT_WOW_VERSION }, orderBy: { versionCode: "desc" }, }); const totalDownloads = versions.reduce((s, v) => s + v.downloadCount, 0); @@ -87,34 +88,21 @@ export default async function AdminSoftwarePage() {
- {(["1.18", "1.17"] as const).map((wv) => { - const latest = item.versions.find( - (v) => v.isLatest && v.wowVersion === wv - ); - const total = item.versions.filter( - (v) => v.wowVersion === wv - ).length; - const downloads = item.versions - .filter((v) => v.wowVersion === wv) - .reduce((s, v) => s + v.downloadCount, 0); - return ( - - - - WoW {wv} - - ({total} 个版本 · {downloads} 次下载) - - - - -

- {latest ? `v${latest.version}` : "未发布"} -

-
-
- ); - })} + + + + WoW {DEFAULT_WOW_VERSION} + + ({item.versions.length} 个版本 · {item.totalDownloads} 次下载) + + + + +

+ {item.latestVersion ? `v${item.latestVersion.version}` : "未发布"} +

+
+
diff --git a/src/app/api/addons/[id]/route.ts b/src/app/api/addons/[id]/route.ts index e841662..833f445 100644 --- a/src/app/api/addons/[id]/route.ts +++ b/src/app/api/addons/[id]/route.ts @@ -11,14 +11,12 @@ export async function GET( const { id } = await params; const lang = getApiLang(request); const wowVersion = getApiWowVersion(request); - const { searchParams } = new URL(request.url); - const wowAll = searchParams.get("wow") === "all"; const addon = await prisma.addon.findFirst({ where: { OR: [{ id }, { slug: id }] }, include: { releases: { - ...(wowAll ? {} : { where: { wowVersion } }), + where: { wowVersion }, orderBy: { createdAt: "desc" }, }, screenshots: { orderBy: { sortOrder: "asc" } }, @@ -49,7 +47,7 @@ export async function GET( descriptionEn: addon.descriptionEn, releases, lang, - wowVersion: wowAll ? "all" : wowVersion, + wowVersion, }); } diff --git a/src/app/api/addons/route.ts b/src/app/api/addons/route.ts index 2aa055c..c551215 100644 --- a/src/app/api/addons/route.ts +++ b/src/app/api/addons/route.ts @@ -11,7 +11,6 @@ export async function GET(request: NextRequest) { const publishedOnly = searchParams.get("published") !== "false"; const lang = getApiLang(request); const wowVersion = getApiWowVersion(request); - const wowAll = searchParams.get("wow") === "all"; const where: Record = {}; if (publishedOnly) where.published = true; @@ -31,7 +30,7 @@ export async function GET(request: NextRequest) { releases: { where: { isLatest: true, - ...(wowAll ? {} : { wowVersion }), + wowVersion, }, take: 1, }, @@ -66,7 +65,7 @@ export async function GET(request: NextRequest) { descriptionEn: a.descriptionEn, releases, lang, - wowVersion: wowAll ? "all" : wowVersion, + wowVersion, }; }); diff --git a/src/app/api/download/[id]/route.ts b/src/app/api/download/[id]/route.ts index c04f62d..a2e29ec 100644 --- a/src/app/api/download/[id]/route.ts +++ b/src/app/api/download/[id]/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { readFile, stat } from "fs/promises"; import path from "path"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export async function GET( _request: NextRequest, @@ -9,8 +10,8 @@ export async function GET( ) { const { id } = await params; - const release = await prisma.release.findUnique({ - where: { id }, + const release = await prisma.release.findFirst({ + where: { id, wowVersion: DEFAULT_WOW_VERSION }, include: { addon: { select: { name: true, slug: true } } }, }); diff --git a/src/app/api/releases/[id]/route.ts b/src/app/api/releases/[id]/route.ts index 5ed5de3..677ca3d 100644 --- a/src/app/api/releases/[id]/route.ts +++ b/src/app/api/releases/[id]/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { auth } from "@/lib/auth"; -import { isWowVersion } from "@/lib/wow-versions"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export async function PUT( request: NextRequest, @@ -22,7 +22,6 @@ export async function PUT( filePath, externalUrl, gameVersion, - wowVersion, isLatest, } = body; @@ -31,11 +30,7 @@ export async function PUT( return NextResponse.json({ error: "Release not found" }, { status: 404 }); } - // Determine the effective wow scope for isLatest demotion. - const newWow = - wowVersion !== undefined && isWowVersion(wowVersion) - ? wowVersion - : existing.wowVersion; + const newWow = DEFAULT_WOW_VERSION; if (isLatest === true && !existing.isLatest) { await prisma.release.updateMany({ @@ -59,9 +54,7 @@ export async function PUT( ...(filePath !== undefined && { filePath }), ...(externalUrl !== undefined && { externalUrl }), ...(gameVersion !== undefined && { gameVersion }), - ...(wowVersion !== undefined && isWowVersion(wowVersion) - ? { wowVersion } - : {}), + wowVersion: DEFAULT_WOW_VERSION, ...(isLatest !== undefined && { isLatest }), }, include: { addon: { select: { name: true, slug: true } } }, diff --git a/src/app/api/releases/route.ts b/src/app/api/releases/route.ts index 34eb0c8..322594b 100644 --- a/src/app/api/releases/route.ts +++ b/src/app/api/releases/route.ts @@ -2,22 +2,17 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { auth } from "@/lib/auth"; import { getApiLang, pickText } from "@/lib/api-locale"; -import { - DEFAULT_WOW_VERSION, - getApiWowVersion, - isWowVersion, -} from "@/lib/wow-versions"; +import { DEFAULT_WOW_VERSION, getApiWowVersion } from "@/lib/wow-versions"; export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const addonId = searchParams.get("addonId"); const lang = getApiLang(request); - const wowAll = searchParams.get("wow") === "all"; const wowVersion = getApiWowVersion(request); const where: Record = {}; if (addonId) where.addonId = addonId; - if (!wowAll) where.wowVersion = wowVersion; + where.wowVersion = wowVersion; const releases = await prisma.release.findMany({ where, @@ -68,7 +63,6 @@ export async function POST(request: NextRequest) { filePath, externalUrl, gameVersion, - wowVersion, } = body; if (!addonId || !version) { @@ -85,7 +79,7 @@ export async function POST(request: NextRequest) { ); } - const wow = isWowVersion(wowVersion) ? wowVersion : DEFAULT_WOW_VERSION; + const wow = DEFAULT_WOW_VERSION; const addon = await prisma.addon.findUnique({ where: { id: addonId } }); if (!addon) { diff --git a/src/app/api/software/[id]/route.ts b/src/app/api/software/[id]/route.ts index 3a1a483..38d93c7 100644 --- a/src/app/api/software/[id]/route.ts +++ b/src/app/api/software/[id]/route.ts @@ -11,14 +11,12 @@ export async function GET( const { id } = await params; const lang = getApiLang(request); const wowVersion = getApiWowVersion(request); - const { searchParams } = new URL(request.url); - const wowAll = searchParams.get("wow") === "all"; const software = await prisma.software.findFirst({ where: { OR: [{ id }, { slug: id }] }, include: { versions: { - ...(wowAll ? {} : { where: { wowVersion } }), + where: { wowVersion }, orderBy: { versionCode: "desc" }, }, }, @@ -43,7 +41,7 @@ export async function GET( changelogEn: v.changelogEn, })), lang, - wowVersion: wowAll ? "all" : wowVersion, + wowVersion, }); } diff --git a/src/app/api/software/[id]/versions/route.ts b/src/app/api/software/[id]/versions/route.ts index 608edfb..91752d4 100644 --- a/src/app/api/software/[id]/versions/route.ts +++ b/src/app/api/software/[id]/versions/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { auth } from "@/lib/auth"; -import { DEFAULT_WOW_VERSION, isWowVersion } from "@/lib/wow-versions"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export async function POST( request: NextRequest, @@ -25,7 +25,6 @@ export async function POST( fileSize, forceUpdate, minVersion, - wowVersion, } = body; if (!version || !versionCode) { @@ -35,7 +34,7 @@ export async function POST( ); } - const wow = isWowVersion(wowVersion) ? wowVersion : DEFAULT_WOW_VERSION; + const wow = DEFAULT_WOW_VERSION; const software = await prisma.software.findUnique({ where: { id: softwareId }, diff --git a/src/app/api/software/changelog/route.ts b/src/app/api/software/changelog/route.ts index 793a0ed..f320ef6 100644 --- a/src/app/api/software/changelog/route.ts +++ b/src/app/api/software/changelog/route.ts @@ -8,8 +8,6 @@ export async function GET(request: NextRequest) { const slug = searchParams.get("slug"); const lang = getApiLang(request); const wowVersion = getApiWowVersion(request); - // Pass `?wow=all` to disable filtering (returns every version) - const wowFilter = searchParams.get("wow") === "all" ? undefined : wowVersion; if (!slug) { return NextResponse.json( @@ -22,7 +20,7 @@ export async function GET(request: NextRequest) { where: { slug }, include: { versions: { - ...(wowFilter ? { where: { wowVersion: wowFilter } } : {}), + where: { wowVersion }, orderBy: { versionCode: "desc" }, select: { version: true, @@ -52,7 +50,7 @@ export async function GET(request: NextRequest) { nameEn: software.nameEn, slug: software.slug, lang, - wowVersion: wowFilter ?? "all", + wowVersion, versions: software.versions.map((v) => ({ version: v.version, versionCode: v.versionCode, diff --git a/src/app/api/software/check-update/route.ts b/src/app/api/software/check-update/route.ts index f2ed764..67e4f38 100644 --- a/src/app/api/software/check-update/route.ts +++ b/src/app/api/software/check-update/route.ts @@ -8,10 +8,8 @@ export async function GET(request: NextRequest) { const slug = searchParams.get("slug"); const currentVersionCode = searchParams.get("versionCode"); const lang = getApiLang(request); - // Self-update endpoint for launchers: ignore cookies. The launcher must - // declare which wow channel it speaks for via ?wow=, otherwise we serve the - // canonical 1.18 channel. This prevents a 1.18 launcher from accidentally - // pulling 1.17 updates (or vice versa) just because of stale cookies. + // Self-update endpoint for launchers: ignore cookies and always serve the + // canonical 1.18 channel. const wowVersion = getDownloadWowVersion(request); if (!slug) { diff --git a/src/app/api/software/download/[id]/route.ts b/src/app/api/software/download/[id]/route.ts index 3949639..64e16e5 100644 --- a/src/app/api/software/download/[id]/route.ts +++ b/src/app/api/software/download/[id]/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { readFile, stat } from "fs/promises"; import path from "path"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export async function GET( request: NextRequest, @@ -10,8 +11,8 @@ export async function GET( const { id } = await params; const source = new URL(request.url).searchParams.get("source"); - const sv = await prisma.softwareVersion.findUnique({ - where: { id }, + const sv = await prisma.softwareVersion.findFirst({ + where: { id, wowVersion: DEFAULT_WOW_VERSION }, include: { software: { select: { slug: true } } }, }); diff --git a/src/app/api/software/versions/[versionId]/route.ts b/src/app/api/software/versions/[versionId]/route.ts index 11afd28..f12a5d0 100644 --- a/src/app/api/software/versions/[versionId]/route.ts +++ b/src/app/api/software/versions/[versionId]/route.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; import { auth } from "@/lib/auth"; -import { isWowVersion } from "@/lib/wow-versions"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export async function PUT( request: NextRequest, @@ -22,12 +22,7 @@ export async function PUT( return NextResponse.json({ error: "Version not found" }, { status: 404 }); } - // Resolve the wow scope after this update applies. If the wow tag is being - // changed, the new tag governs which siblings get demoted. - const newWow = - body.wowVersion !== undefined && isWowVersion(body.wowVersion) - ? body.wowVersion - : existing.wowVersion; + const newWow = DEFAULT_WOW_VERSION; const setLatest = body.isLatest === true && !existing.isLatest; if (setLatest) { @@ -65,9 +60,7 @@ export async function PUT( minVersion: body.minVersion || null, }), ...(body.isLatest !== undefined && { isLatest: body.isLatest }), - ...(body.wowVersion !== undefined && isWowVersion(body.wowVersion) - ? { wowVersion: body.wowVersion } - : {}), + wowVersion: DEFAULT_WOW_VERSION, }, }); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3e0ea1e..dccdd02 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,9 +2,7 @@ import type { Metadata } from "next"; import { Toaster } from "@/components/ui/sonner"; import { ThemeProvider } from "@/components/ThemeProvider"; import { LocaleProvider } from "@/i18n/LocaleProvider"; -import { WowVersionProvider } from "@/i18n/WowVersionProvider"; import { getServerLocale } from "@/i18n/getLocale"; -import { getServerWowVersion } from "@/lib/get-server-wow"; import "./globals.css"; export const metadata: Metadata = { @@ -31,20 +29,15 @@ export default async function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const [locale, wowVersion] = await Promise.all([ - getServerLocale(), - getServerWowVersion(), - ]); + const locale = await getServerLocale(); return ( - - - {children} - - - + + {children} + + diff --git a/src/components/admin/ReleaseForm.tsx b/src/components/admin/ReleaseForm.tsx index 671e2bc..3a14d87 100644 --- a/src/components/admin/ReleaseForm.tsx +++ b/src/components/admin/ReleaseForm.tsx @@ -16,6 +16,7 @@ import { import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { toast } from "sonner"; import { Upload } from "lucide-react"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; interface ReleaseFormProps { addons: { id: string; name: string }[]; @@ -25,7 +26,6 @@ export function ReleaseForm({ addons }: ReleaseFormProps) { const router = useRouter(); const [loading, setLoading] = useState(false); const [downloadType, setDownloadType] = useState("local"); - const [wowVersion, setWowVersion] = useState<"1.18" | "1.17">("1.18"); const [uploadedFilePath, setUploadedFilePath] = useState(""); const [uploading, setUploading] = useState(false); const [selectedAddonId, setSelectedAddonId] = useState(""); @@ -63,7 +63,7 @@ export function ReleaseForm({ addons }: ReleaseFormProps) { version: formData.get("version"), changelog: formData.get("changelog"), changelogEn: formData.get("changelogEn") || "", - wowVersion, + wowVersion: DEFAULT_WOW_VERSION, downloadType, filePath: downloadType === "local" ? uploadedFilePath : null, externalUrl: @@ -153,28 +153,6 @@ export function ReleaseForm({ addons }: ReleaseFormProps) { placeholder="例如 1.18.1" /> -
- -
- {(["1.18", "1.17"] as const).map((v) => ( - - ))} -
-

- 插件按 WoW 客户端版本独立维护,发布到对应通道 -

-
diff --git a/src/components/admin/ReleasesTable.tsx b/src/components/admin/ReleasesTable.tsx index 3e35ebf..08d9f67 100644 --- a/src/components/admin/ReleasesTable.tsx +++ b/src/components/admin/ReleasesTable.tsx @@ -17,6 +17,7 @@ import { } from "@/components/ui/table"; import { toast } from "sonner"; import { Download, Pencil, Trash2, Check, X, Upload, Star } from "lucide-react"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; interface Release { id: string; @@ -70,7 +71,6 @@ export function ReleasesTable({ releases: initial }: { releases: Release[] }) { 插件 版本 - WoW 游戏版本 下载方式 下载量 @@ -82,7 +82,7 @@ export function ReleasesTable({ releases: initial }: { releases: Release[] }) { {initial.length === 0 ? ( - + 暂无版本发布 @@ -91,11 +91,6 @@ export function ReleasesTable({ releases: initial }: { releases: Release[] }) { {r.addon.name} v{r.version} - - - {r.wowVersion} - - {r.gameVersion || "-"} @@ -182,9 +177,6 @@ function ReleaseEditPanel({ const [version, setVersion] = useState(r.version); const [changelog, setChangelog] = useState(r.changelog); const [changelogEn, setChangelogEn] = useState(r.changelogEn || ""); - const [wowVersion, setWowVersion] = useState<"1.18" | "1.17">( - (r.wowVersion as "1.18" | "1.17") || "1.18" - ); const [gameVersion, setGameVersion] = useState(r.gameVersion); const [downloadType, setDownloadType] = useState(r.downloadType); const [externalUrl, setExternalUrl] = useState(r.externalUrl || ""); @@ -228,7 +220,7 @@ function ReleaseEditPanel({ version, changelog, changelogEn, - wowVersion, + wowVersion: DEFAULT_WOW_VERSION, gameVersion, downloadType, filePath: downloadType === "local" ? filePath : null, @@ -258,7 +250,7 @@ function ReleaseEditPanel({
-
+
setVersion(e.target.value)} /> @@ -271,25 +263,6 @@ function ReleaseEditPanel({ placeholder="1.18.1" />
-
- -
- {(["1.18", "1.17"] as const).map((wv) => ( - - ))} -
-
diff --git a/src/components/admin/SoftwareEditForm.tsx b/src/components/admin/SoftwareEditForm.tsx index 20a4c09..9f9f72c 100644 --- a/src/components/admin/SoftwareEditForm.tsx +++ b/src/components/admin/SoftwareEditForm.tsx @@ -10,6 +10,7 @@ import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { toast } from "sonner"; import { Pencil, Trash2, Upload, X, Check } from "lucide-react"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; interface SoftwareVersion { id: string; @@ -143,9 +144,6 @@ function VersionItem({ version: v }: { version: SoftwareVersion }) { const [versionCode, setVersionCode] = useState(v.versionCode.toString()); const [changelog, setChangelog] = useState(v.changelog); const [changelogEn, setChangelogEn] = useState(v.changelogEn || ""); - const [wowVersion, setWowVersion] = useState<"1.18" | "1.17">( - (v.wowVersion as "1.18" | "1.17") || "1.18" - ); const [downloadType, setDownloadType] = useState(v.downloadType); const [externalUrl, setExternalUrl] = useState(v.externalUrl || ""); const [forceUpdate, setForceUpdate] = useState(v.forceUpdate); @@ -192,7 +190,7 @@ function VersionItem({ version: v }: { version: SoftwareVersion }) { versionCode: Number(versionCode), changelog, changelogEn, - wowVersion, + wowVersion: DEFAULT_WOW_VERSION, downloadType, filePath: downloadType === "local" ? filePath : null, externalUrl: downloadType === "url" ? externalUrl : null, @@ -241,7 +239,7 @@ function VersionItem({ version: v }: { version: SoftwareVersion }) { (code: {v.versionCode}) - WoW {v.wowVersion} + WoW {DEFAULT_WOW_VERSION} {v.isLatest && 最新} {v.forceUpdate && ( @@ -327,25 +325,6 @@ function VersionItem({ version: v }: { version: SoftwareVersion }) { placeholder="可选" />
-
- -
- {(["1.18", "1.17"] as const).map((wv) => ( - - ))} -
-
diff --git a/src/components/admin/SoftwareVersionForm.tsx b/src/components/admin/SoftwareVersionForm.tsx index f404666..e6d6315 100644 --- a/src/components/admin/SoftwareVersionForm.tsx +++ b/src/components/admin/SoftwareVersionForm.tsx @@ -9,12 +9,12 @@ import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { toast } from "sonner"; import { Upload } from "lucide-react"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export function SoftwareVersionForm({ softwareId }: { softwareId: string }) { const router = useRouter(); const [loading, setLoading] = useState(false); const [downloadType, setDownloadType] = useState("local"); - const [wowVersion, setWowVersion] = useState<"1.18" | "1.17">("1.18"); const [uploadedFilePath, setUploadedFilePath] = useState(""); const [fileSize, setFileSize] = useState(0); const [uploading, setUploading] = useState(false); @@ -65,7 +65,7 @@ export function SoftwareVersionForm({ softwareId }: { softwareId: string }) { fileSize, forceUpdate: fd.get("forceUpdate") === "on", minVersion: fd.get("minVersion") || null, - wowVersion, + wowVersion: DEFAULT_WOW_VERSION, }), }); @@ -85,7 +85,7 @@ export function SoftwareVersionForm({ softwareId }: { softwareId: string }) { 版本信息
-
+
@@ -94,28 +94,6 @@ export function SoftwareVersionForm({ softwareId }: { softwareId: string }) {
-
- -
- {(["1.18", "1.17"] as const).map((v) => ( - - ))} -
-

- 同一启动器的两个 WoW 客户端发行渠道独立维护 -

-
diff --git a/src/components/admin/SoftwareVersionTable.tsx b/src/components/admin/SoftwareVersionTable.tsx index e168a95..5568f98 100644 --- a/src/components/admin/SoftwareVersionTable.tsx +++ b/src/components/admin/SoftwareVersionTable.tsx @@ -25,6 +25,7 @@ import { Upload, Star, } from "lucide-react"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; interface Version { id: string; @@ -86,7 +87,6 @@ export function SoftwareVersionTable({ 版本 - WoW 版本代码 下载方式 直接下载 @@ -100,7 +100,7 @@ export function SoftwareVersionTable({ 暂无版本,点击上方按钮发布第一个版本 @@ -117,7 +117,6 @@ export function SoftwareVersionTable({ 版本 - WoW 版本代码 下载方式 直接下载 @@ -132,11 +131,6 @@ export function SoftwareVersionTable({ {initialVersions.map((v) => ( v{v.version} - - - {v.wowVersion} - - {v.versionCode} @@ -234,9 +228,6 @@ function VersionEditPanel({ const [versionCode, setVersionCode] = useState(v.versionCode.toString()); const [changelog, setChangelog] = useState(v.changelog); const [changelogEn, setChangelogEn] = useState(v.changelogEn || ""); - const [wowVersion, setWowVersion] = useState<"1.18" | "1.17">( - (v.wowVersion as "1.18" | "1.17") || "1.18" - ); const [downloadType, setDownloadType] = useState(v.downloadType); const [externalUrl, setExternalUrl] = useState(v.externalUrl || ""); const [forceUpdate, setForceUpdate] = useState(v.forceUpdate); @@ -284,7 +275,7 @@ function VersionEditPanel({ versionCode: Number(versionCode), changelog, changelogEn, - wowVersion, + wowVersion: DEFAULT_WOW_VERSION, downloadType, filePath: downloadType === "local" ? filePath : null, externalUrl: downloadType === "url" ? externalUrl : null, @@ -339,25 +330,6 @@ function VersionEditPanel({ placeholder="可选" />
-
- -
- {(["1.18", "1.17"] as const).map((wv) => ( - - ))} -
-
diff --git a/src/components/public/Footer.tsx b/src/components/public/Footer.tsx index a52b882..b44b05b 100644 --- a/src/components/public/Footer.tsx +++ b/src/components/public/Footer.tsx @@ -3,11 +3,11 @@ import Link from "next/link"; import { Package, Download, FileText, Clock } from "lucide-react"; import { useLocale } from "@/i18n/LocaleProvider"; -import { useWowVersion } from "@/i18n/WowVersionProvider"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; export function Footer() { const { t } = useLocale(); - const { wowVersion } = useWowVersion(); + const wowVersion = DEFAULT_WOW_VERSION; const quickLinks = [ { href: "/addons", label: t("footer", "addons"), icon: Package }, diff --git a/src/components/public/HeroBanner.tsx b/src/components/public/HeroBanner.tsx index 38fce96..b79f611 100644 --- a/src/components/public/HeroBanner.tsx +++ b/src/components/public/HeroBanner.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState, useCallback } from "react"; import { Download, ChevronRight } from "lucide-react"; import { useLocale } from "@/i18n/LocaleProvider"; -import { useWowVersion } from "@/i18n/WowVersionProvider"; +import { DEFAULT_WOW_VERSION } from "@/lib/wow-versions"; interface Particle { x: number; @@ -48,7 +48,7 @@ export function HeroBanner({ banners?: { imageUrl: string }[]; }) { const { t } = useLocale(); - const { wowVersion } = useWowVersion(); + const wowVersion = DEFAULT_WOW_VERSION; const slides = banners && banners.length > 0 ? banners.map((b) => ({ image: b.imageUrl })) diff --git a/src/components/public/Navbar.tsx b/src/components/public/Navbar.tsx index f164dbf..a144a11 100644 --- a/src/components/public/Navbar.tsx +++ b/src/components/public/Navbar.tsx @@ -6,7 +6,6 @@ import { usePathname } from "next/navigation"; import { Package, Menu, X } from "lucide-react"; import { useLocale } from "@/i18n/LocaleProvider"; import { LanguageSwitcher } from "./LanguageSwitcher"; -import { WowVersionSwitcher } from "./WowVersionSwitcher"; export function Navbar() { const [open, setOpen] = useState(false); @@ -51,7 +50,6 @@ export function Navbar() { ); })} - @@ -86,7 +84,6 @@ export function Navbar() { ); })}
-
diff --git a/src/components/public/WowVersionSwitcher.tsx b/src/components/public/WowVersionSwitcher.tsx deleted file mode 100644 index e26f6aa..0000000 --- a/src/components/public/WowVersionSwitcher.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import { Gamepad2 } from "lucide-react"; -import { useWowVersion } from "@/i18n/WowVersionProvider"; - -export function WowVersionSwitcher({ - variant = "navbar", -}: { - variant?: "navbar" | "mobile"; -}) { - const { wowVersion, setWowVersion, versions } = useWowVersion(); - - if (variant === "mobile") { - return ( -
- - WoW -
- {versions.map((v) => ( - - ))} -
-
- ); - } - - return ( -
- - {versions.map((v) => ( - - ))} -
- ); -} diff --git a/src/i18n/WowVersionProvider.tsx b/src/i18n/WowVersionProvider.tsx deleted file mode 100644 index 810614c..0000000 --- a/src/i18n/WowVersionProvider.tsx +++ /dev/null @@ -1,68 +0,0 @@ -"use client"; - -import { - createContext, - useCallback, - useContext, - useMemo, - useState, - type ReactNode, -} from "react"; -import { - WOW_COOKIE, - WOW_VERSIONS, - type WowVersion, -} from "@/lib/wow-versions"; - -interface WowVersionContextValue { - wowVersion: WowVersion; - setWowVersion: (next: WowVersion) => void; - versions: readonly WowVersion[]; -} - -const WowVersionContext = createContext(null); - -export function WowVersionProvider({ - initial, - children, -}: { - initial: WowVersion; - children: ReactNode; -}) { - const [wowVersion, setLocal] = useState(initial); - - const setWowVersion = useCallback((next: WowVersion) => { - setLocal(next); - if (typeof document !== "undefined") { - document.cookie = `${WOW_COOKIE}=${next}; path=/; max-age=${ - 60 * 60 * 24 * 365 - }; samesite=lax`; - // Server components rely on the cookie — reload so the SSR'd lists refresh. - // Use a microtask so React state has a chance to commit first. - setTimeout(() => { - if (typeof window !== "undefined") { - window.location.reload(); - } - }, 0); - } - }, []); - - const value = useMemo( - () => ({ wowVersion, setWowVersion, versions: WOW_VERSIONS }), - [wowVersion, setWowVersion] - ); - - return ( - - {children} - - ); -} - -export function useWowVersion() { - const ctx = useContext(WowVersionContext); - if (!ctx) { - throw new Error("useWowVersion must be used inside "); - } - return ctx; -} diff --git a/src/lib/get-server-wow.ts b/src/lib/get-server-wow.ts deleted file mode 100644 index 5f14520..0000000 --- a/src/lib/get-server-wow.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { cookies } from "next/headers"; -import { - DEFAULT_WOW_VERSION, - WOW_COOKIE, - isWowVersion, - type WowVersion, -} from "./wow-versions"; - -/** - * Resolve the wow version from the request cookie on the server. - * Used by server components to filter DB queries before render. - */ -export async function getServerWowVersion(): Promise { - const store = await cookies(); - const v = store.get(WOW_COOKIE)?.value; - if (isWowVersion(v)) return v; - return DEFAULT_WOW_VERSION; -} diff --git a/src/lib/wow-versions.ts b/src/lib/wow-versions.ts index b242cb6..f96fd52 100644 --- a/src/lib/wow-versions.ts +++ b/src/lib/wow-versions.ts @@ -1,70 +1,37 @@ /** * Turtle WoW client major-version constants and request resolvers. * - * The platform tracks separate addon / launcher builds for each supported - * client version. Each Release / SoftwareVersion is tagged with one of - * these strings, and APIs accept `?wow=` to filter. + * The public site, admin console, and download APIs currently expose only the + * 1.18 channel. Historical rows may still carry a wowVersion tag in the + * database, but request resolvers intentionally collapse every request to 1.18. */ import type { NextRequest } from "next/server"; -export const WOW_VERSIONS = ["1.18", "1.17"] as const; +export const WOW_VERSIONS = ["1.18"] as const; export type WowVersion = (typeof WOW_VERSIONS)[number]; export const DEFAULT_WOW_VERSION: WowVersion = "1.18"; -export const WOW_COOKIE = "wow"; export function isWowVersion(v: unknown): v is WowVersion { return typeof v === "string" && (WOW_VERSIONS as readonly string[]).includes(v); } /** - * Resolve the requested wow version on a public listing/browsing API. - * - * 1. ?wow=1.18|1.17 - * 2. ?wowVersion=1.18|1.17 (alias) - * 3. cookie `wow` - * 4. Default DEFAULT_WOW_VERSION - * - * Used for endpoints whose response shape depends on the user's currently - * selected wow channel (addons list, releases list, etc.). The cookie lets a - * browser session display the right list across navigations. + * Resolve the requested wow version on a public listing/browsing API. Only + * the canonical 1.18 channel is supported, so explicit legacy values are + * ignored instead of changing the query scope. */ -export function getApiWowVersion(request: NextRequest | URL): WowVersion { - const url = request instanceof URL ? request : new URL(request.url); - const explicit = - url.searchParams.get("wow") || url.searchParams.get("wowVersion"); - if (isWowVersion(explicit)) return explicit; - - if (!(request instanceof URL)) { - const cookieHeader = request.headers.get("cookie") || ""; - const m = cookieHeader.match(/(?:^|;\s*)wow=([^;]+)/); - if (m && isWowVersion(m[1])) return m[1] as WowVersion; - } - +export function getApiWowVersion(_request: NextRequest | URL): WowVersion { return DEFAULT_WOW_VERSION; } /** - * Resolve the wow version for **download endpoints**. - * - * 1. ?wow=1.18|1.17 (explicit only) - * 2. ?wowVersion=1.18|1.17 (alias) - * 3. Default DEFAULT_WOW_VERSION - * - * Cookies are deliberately **NOT** consulted — download URLs must be fully - * determined by the URL itself so that: - * - * - third-party links (`/download/launcher`) always serve the canonical 1.18 - * build regardless of the visitor's previous browsing state, and - * - which binary you get is never tied to language or any other cookie state. + * Resolve the wow version for download endpoints. Download URLs are now pinned + * to 1.18 regardless of query params, cookies, or historical client channels. */ export function getDownloadWowVersion( - request: NextRequest | URL + _request: NextRequest | URL ): WowVersion { - const url = request instanceof URL ? request : new URL(request.url); - const explicit = - url.searchParams.get("wow") || url.searchParams.get("wowVersion"); - if (isWowVersion(explicit)) return explicit; return DEFAULT_WOW_VERSION; }