diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 931733a..31f2a63 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -27,6 +27,7 @@ import { Suspense, useCallback, useEffect, useState } from 'react'; import Swal from 'sweetalert2'; import { AdminConfig, AdminConfigResult } from '@/lib/admin.types'; +import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; import PageLayout from '@/components/PageLayout'; @@ -118,8 +119,7 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { }); // 当前登录用户名 - const currentUsername = - typeof window !== 'undefined' ? localStorage.getItem('username') : null; + const currentUsername = getAuthInfoFromBrowserCookie()?.username || null; useEffect(() => { if (config?.UserConfig) { @@ -131,15 +131,6 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { // 切换允许注册设置 const toggleAllowRegister = async (value: boolean) => { - const username = - typeof window !== 'undefined' ? localStorage.getItem('username') : null; - const password = - typeof window !== 'undefined' ? localStorage.getItem('password') : null; - if (!username || !password) { - showError('无法获取当前用户信息,请重新登录'); - return; - } - try { // 先更新本地 UI setUserSettings((prev) => ({ ...prev, enableRegistration: value })); @@ -148,8 +139,6 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - username, - password, action: 'setAllowRegister', allowRegister: value, }), @@ -197,23 +186,11 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => { targetUsername: string, targetPassword?: string ) => { - const username = - typeof window !== 'undefined' ? localStorage.getItem('username') : null; - const password = - typeof window !== 'undefined' ? localStorage.getItem('password') : null; - - if (!username || !password) { - showError('无法获取当前用户信息,请重新登录'); - return; - } - try { const res = await fetch('/api/admin/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - username, - password, targetUsername, ...(targetPassword ? { targetPassword } : {}), action, @@ -521,21 +498,11 @@ const VideoSourceConfig = ({ // 通用 API 请求 const callSourceApi = async (body: Record) => { - const username = - typeof window !== 'undefined' ? localStorage.getItem('username') : null; - const password = - typeof window !== 'undefined' ? localStorage.getItem('password') : null; - - if (!username || !password) { - showError('无法获取当前用户信息,请重新登录'); - throw new Error('no-credential'); - } - try { const resp = await fetch('/api/admin/source', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password, ...body }), + body: JSON.stringify({ ...body }), }); if (!resp.ok) { @@ -845,26 +812,12 @@ const SiteConfigComponent = ({ config }: { config: AdminConfig | null }) => { // 保存站点配置 const handleSave = async () => { - const username = - typeof window !== 'undefined' ? localStorage.getItem('username') : null; - if (!username) { - showError('无法获取用户名,请重新登录'); - return; - } - - const password = - typeof window !== 'undefined' ? localStorage.getItem('password') : null; - if (!password) { - showError('无法获取密码,请重新登录'); - return; - } - try { setSaving(true); const resp = await fetch('/api/admin/site', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password, ...siteSettings }), + body: JSON.stringify({ ...siteSettings }), }); if (!resp.ok) { @@ -1024,12 +977,7 @@ function AdminPageClient() { setLoading(true); } - const username = localStorage.getItem('username'); - const response = await fetch( - `/api/admin/config${ - username ? `?username=${encodeURIComponent(username)}` : '' - }` - ); + const response = await fetch(`/api/admin/config`); if (!response.ok) { const data = (await response.json()) as any; @@ -1065,11 +1013,6 @@ function AdminPageClient() { // 新增: 重置配置处理函数 const handleResetConfig = async () => { - const username = localStorage.getItem('username'); - if (!username) { - showError('无法获取用户名,请重新登录'); - return; - } const { isConfirmed } = await Swal.fire({ title: '确认重置配置', text: '此操作将重置用户封禁和管理员设置、自定义视频源,站点配置将重置为默认值,是否继续?', @@ -1080,22 +1023,8 @@ function AdminPageClient() { }); if (!isConfirmed) return; - const password = localStorage.getItem('password'); - if (!password) { - showError('无法获取密码,请重新登录'); - return; - } - try { - const response = await fetch( - `/api/admin/reset${ - username - ? `?username=${encodeURIComponent( - username - )}&password=${encodeURIComponent(password)}` - : '' - }` - ); + const response = await fetch(`/api/admin/reset`); if (!response.ok) { throw new Error(`重置失败: ${response.status}`); } diff --git a/src/app/api/admin/config/route.ts b/src/app/api/admin/config/route.ts index 35c584c..2167217 100644 --- a/src/app/api/admin/config/route.ts +++ b/src/app/api/admin/config/route.ts @@ -1,13 +1,14 @@ /* eslint-disable no-console */ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; import { AdminConfigResult } from '@/lib/admin.types'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { getConfig } from '@/lib/config'; export const runtime = 'edge'; -export async function GET(request: Request) { +export async function GET(request: NextRequest) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; if (storageType === 'localstorage') { return NextResponse.json( @@ -18,8 +19,11 @@ export async function GET(request: Request) { ); } - const { searchParams } = new URL(request.url); - const username = searchParams.get('username'); + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const username = authInfo.username; try { const config = getConfig(); diff --git a/src/app/api/admin/reset/route.ts b/src/app/api/admin/reset/route.ts index 8e78d35..e5c7914 100644 --- a/src/app/api/admin/reset/route.ts +++ b/src/app/api/admin/reset/route.ts @@ -1,12 +1,13 @@ /* eslint-disable no-console */ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { resetConfig } from '@/lib/config'; export const runtime = 'edge'; -export async function GET(request: Request) { +export async function GET(request: NextRequest) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; if (storageType === 'localstorage') { return NextResponse.json( @@ -17,18 +18,13 @@ export async function GET(request: Request) { ); } - const { searchParams } = new URL(request.url); - const username = searchParams.get('username'); - const password = searchParams.get('password'); - - if (!username || !password) { - return NextResponse.json( - { error: '用户名和密码不能为空' }, - { status: 400 } - ); + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } + const username = authInfo.username; - if (username !== process.env.USERNAME || password !== process.env.PASSWORD) { + if (username !== process.env.USERNAME) { return NextResponse.json({ error: '仅支持站长重置配置' }, { status: 401 }); } diff --git a/src/app/api/admin/site/route.ts b/src/app/api/admin/site/route.ts index eb4a362..8b125f6 100644 --- a/src/app/api/admin/site/route.ts +++ b/src/app/api/admin/site/route.ts @@ -1,13 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any,no-console */ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { getConfig } from '@/lib/config'; import { getStorage } from '@/lib/db'; export const runtime = 'edge'; -export async function POST(request: Request) { +export async function POST(request: NextRequest) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; if (storageType === 'localstorage') { return NextResponse.json( @@ -21,17 +22,19 @@ export async function POST(request: Request) { try { const body = await request.json(); + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const username = authInfo.username; + const { - username, - password, SiteName, Announcement, SearchDownstreamMaxPage, SiteInterfaceCacheTime, SearchResultDefaultAggregate, } = body as { - username?: string; - password?: string; SiteName: string; Announcement: string; SearchDownstreamMaxPage: number; @@ -50,23 +53,11 @@ export async function POST(request: Request) { return NextResponse.json({ error: '参数格式错误' }, { status: 400 }); } - if (!username || !password) { - return NextResponse.json( - { error: '用户名和密码不能为空' }, - { status: 400 } - ); - } - const adminConfig = getConfig(); const storage = getStorage(); - // 权限与密码校验 - if (username === process.env.USERNAME) { - // 站长 - if (password !== process.env.PASSWORD) { - return NextResponse.json({ error: '密码错误' }, { status: 401 }); - } - } else { + // 权限校验 + if (username !== process.env.USERNAME) { // 管理员 const user = adminConfig.UserConfig.Users.find( (u) => u.username === username @@ -74,18 +65,6 @@ export async function POST(request: Request) { if (!user || user.role !== 'admin') { return NextResponse.json({ error: '权限不足' }, { status: 401 }); } - - if (!storage || typeof storage.verifyUser !== 'function') { - return NextResponse.json( - { error: '存储未配置用户认证' }, - { status: 500 } - ); - } - - const ok = await storage.verifyUser(username, password); - if (!ok) { - return NextResponse.json({ error: '密码错误' }, { status: 401 }); - } } // 更新缓存中的站点设置 diff --git a/src/app/api/admin/source/route.ts b/src/app/api/admin/source/route.ts index 1c24663..af9d77e 100644 --- a/src/app/api/admin/source/route.ts +++ b/src/app/api/admin/source/route.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any,no-console */ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { getConfig } from '@/lib/config'; import { getStorage } from '@/lib/db'; import { IStorage } from '@/lib/types'; @@ -12,12 +13,10 @@ export const runtime = 'edge'; type Action = 'add' | 'disable' | 'enable' | 'delete' | 'sort'; interface BaseBody { - username?: string; - password?: string; action?: Action; } -export async function POST(request: Request) { +export async function POST(request: NextRequest) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; if (storageType === 'localstorage') { return NextResponse.json( @@ -30,12 +29,17 @@ export async function POST(request: Request) { try { const body = (await request.json()) as BaseBody & Record; + const { action } = body; - const { username, password, action } = body; + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const username = authInfo.username; // 基础校验 const ACTIONS: Action[] = ['add', 'disable', 'enable', 'delete', 'sort']; - if (!username || !password || !action || !ACTIONS.includes(action)) { + if (!username || !action || !ACTIONS.includes(action)) { return NextResponse.json({ error: '参数格式错误' }, { status: 400 }); } @@ -44,33 +48,13 @@ export async function POST(request: Request) { const storage: IStorage | null = getStorage(); // 权限与身份校验 - if (username === process.env.USERNAME) { - if (password !== process.env.PASSWORD) { - return NextResponse.json( - { error: '用户名或密码错误' }, - { status: 401 } - ); - } - } else { + if (username !== process.env.USERNAME) { const userEntry = adminConfig.UserConfig.Users.find( (u) => u.username === username ); if (!userEntry || userEntry.role !== 'admin') { return NextResponse.json({ error: '权限不足' }, { status: 401 }); } - if (!storage || typeof storage.verifyUser !== 'function') { - return NextResponse.json( - { error: '存储未配置用户认证' }, - { status: 500 } - ); - } - const pass = await storage.verifyUser(username, password); - if (!pass) { - return NextResponse.json( - { error: '用户名或密码错误' }, - { status: 401 } - ); - } } switch (action) { diff --git a/src/app/api/admin/user/route.ts b/src/app/api/admin/user/route.ts index e4ce7a3..681eda1 100644 --- a/src/app/api/admin/user/route.ts +++ b/src/app/api/admin/user/route.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any,no-console,@typescript-eslint/no-non-null-assertion */ -import { NextResponse } from 'next/server'; +import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { getConfig } from '@/lib/config'; import { getStorage } from '@/lib/db'; import { IStorage } from '@/lib/types'; @@ -18,7 +19,7 @@ const ACTIONS = [ 'setAllowRegister', ] as const; -export async function POST(request: Request) { +export async function POST(request: NextRequest) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; if (storageType === 'localstorage') { return NextResponse.json( @@ -32,23 +33,25 @@ export async function POST(request: Request) { try { const body = await request.json(); + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const username = authInfo.username; + const { - username, // 操作者用户名 - password, // 操作者密码 targetUsername, // 目标用户名 targetPassword, // 目标用户密码(仅在添加用户时需要) allowRegister, action, } = body as { - username?: string; - password?: string; targetUsername?: string; targetPassword?: string; allowRegister?: boolean; action?: (typeof ACTIONS)[number]; }; - if (!username || !password || !action || !ACTIONS.includes(action)) { + if (!action || !ACTIONS.includes(action)) { return NextResponse.json({ error: '参数格式错误' }, { status: 400 }); } @@ -71,12 +74,6 @@ export async function POST(request: Request) { let operatorRole: 'owner' | 'admin'; if (username === process.env.USERNAME) { operatorRole = 'owner'; - if (password !== process.env.PASSWORD) { - return NextResponse.json( - { error: '用户名或密码错误' }, - { status: 401 } - ); - } } else { const userEntry = adminConfig.UserConfig.Users.find( (u) => u.username === username @@ -85,20 +82,6 @@ export async function POST(request: Request) { return NextResponse.json({ error: '权限不足' }, { status: 401 }); } operatorRole = 'admin'; - - if (!storage || typeof storage.verifyUser !== 'function') { - return NextResponse.json( - { error: '存储未配置用户认证' }, - { status: 500 } - ); - } - const ok = await storage.verifyUser(username, password); - if (!ok) { - return NextResponse.json( - { error: '用户名或密码错误' }, - { status: 401 } - ); - } } // 查找目标用户条目 diff --git a/src/app/api/favorites/route.ts b/src/app/api/favorites/route.ts index 8f806fd..5c20354 100644 --- a/src/app/api/favorites/route.ts +++ b/src/app/api/favorites/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { db } from '@/lib/db'; import { Favorite } from '@/lib/types'; @@ -16,13 +17,14 @@ export const runtime = 'edge'; */ export async function GET(request: NextRequest) { try { + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const { searchParams } = new URL(request.url); const key = searchParams.get('key'); - const user = searchParams.get('user'); - - if (!user) { - return NextResponse.json({ error: 'Missing user' }, { status: 400 }); - } // 查询单条收藏 if (key) { @@ -33,12 +35,12 @@ export async function GET(request: NextRequest) { { status: 400 } ); } - const fav = await db.getFavorite(user, source, id); + const fav = await db.getFavorite(authInfo.username, source, id); return NextResponse.json(fav, { status: 200 }); } // 查询全部收藏 - const favorites = await db.getAllFavorites(user); + const favorites = await db.getAllFavorites(authInfo.username); return NextResponse.json(favorites, { status: 200 }); } catch (err) { console.error('获取收藏失败', err); @@ -51,21 +53,19 @@ export async function GET(request: NextRequest) { /** * POST /api/favorites - * body: { user?: string; key: string; favorite: Favorite } + * body: { key: string; favorite: Favorite } */ export async function POST(request: NextRequest) { try { - const body = await request.json(); - const { - user, - key, - favorite, - }: { user?: string; key: string; favorite: Favorite } = body; - - if (!user) { - return NextResponse.json({ error: 'Missing user' }, { status: 400 }); + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } + const body = await request.json(); + const { key, favorite }: { key: string; favorite: Favorite } = body; + if (!key || !favorite) { return NextResponse.json( { error: 'Missing key or favorite' }, @@ -94,7 +94,7 @@ export async function POST(request: NextRequest) { save_time: favorite.save_time ?? Date.now(), } as Omit; - await db.saveFavorite(user, source, id, favoriteWithoutUserId); + await db.saveFavorite(authInfo.username, source, id, favoriteWithoutUserId); return NextResponse.json({ success: true }, { status: 200 }); } catch (err) { @@ -114,13 +114,15 @@ export async function POST(request: NextRequest) { */ export async function DELETE(request: NextRequest) { try { + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const username = authInfo.username; const { searchParams } = new URL(request.url); const key = searchParams.get('key'); - const user = searchParams.get('user'); - - if (!user) { - return NextResponse.json({ error: 'Missing user' }, { status: 400 }); - } if (key) { // 删除单条 @@ -131,14 +133,14 @@ export async function DELETE(request: NextRequest) { { status: 400 } ); } - await db.deleteFavorite(user, source, id); + await db.deleteFavorite(username, source, id); } else { // 清空全部 - const all = await db.getAllFavorites(user); + const all = await db.getAllFavorites(username); await Promise.all( Object.keys(all).map(async (k) => { const [s, i] = k.split('+'); - if (s && i) await db.deleteFavorite(user, s, i); + if (s && i) await db.deleteFavorite(username, s, i); }) ); } diff --git a/src/app/api/playrecords/route.ts b/src/app/api/playrecords/route.ts index 2fafd96..cebd43e 100644 --- a/src/app/api/playrecords/route.ts +++ b/src/app/api/playrecords/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { db } from '@/lib/db'; import { PlayRecord } from '@/lib/types'; @@ -9,13 +10,13 @@ export const runtime = 'edge'; export async function GET(request: NextRequest) { try { - const { searchParams } = new URL(request.url); - const user = searchParams.get('user'); - if (!user) { - return NextResponse.json({ error: 'Missing user' }, { status: 400 }); + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const records = await db.getAllPlayRecords(user); + const records = await db.getAllPlayRecords(authInfo.username); return NextResponse.json(records, { status: 200 }); } catch (err) { console.error('获取播放记录失败', err); @@ -28,17 +29,15 @@ export async function GET(request: NextRequest) { export async function POST(request: NextRequest) { try { - const body = await request.json(); - const { - user, - key, - record, - }: { user?: string; key: string; record: PlayRecord } = body; - - if (!user) { - return NextResponse.json({ error: 'Missing user' }, { status: 400 }); + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } + const body = await request.json(); + const { key, record }: { key: string; record: PlayRecord } = body; + if (!key || !record) { return NextResponse.json( { error: 'Missing key or record' }, @@ -75,7 +74,7 @@ export async function POST(request: NextRequest) { save_time: record.save_time, }; - await db.savePlayRecord(user, source, id, recordWithoutUserId); + await db.savePlayRecord(authInfo.username, source, id, recordWithoutUserId); return NextResponse.json({ success: true }, { status: 200 }); } catch (err) { @@ -89,13 +88,15 @@ export async function POST(request: NextRequest) { export async function DELETE(request: NextRequest) { try { + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const username = authInfo.username; const { searchParams } = new URL(request.url); const key = searchParams.get('key'); - const user = searchParams.get('user'); - - if (!user) { - return NextResponse.json({ error: 'Missing user' }, { status: 400 }); - } if (key) { // 如果提供了 key,删除单条播放记录 @@ -107,15 +108,15 @@ export async function DELETE(request: NextRequest) { ); } - await db.deletePlayRecord(user, source, id); + await db.deletePlayRecord(username, source, id); } else { // 未提供 key,则清空全部播放记录 // 目前 DbManager 没有对应方法,这里直接遍历删除 - const all = await db.getAllPlayRecords(user); + const all = await db.getAllPlayRecords(username); await Promise.all( Object.keys(all).map(async (k) => { const [s, i] = k.split('+'); - if (s && i) await db.deletePlayRecord(user, s, i); + if (s && i) await db.deletePlayRecord(username, s, i); }) ); } diff --git a/src/app/api/searchhistory/route.ts b/src/app/api/searchhistory/route.ts index d1cd904..3e372ba 100644 --- a/src/app/api/searchhistory/route.ts +++ b/src/app/api/searchhistory/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; import { db } from '@/lib/db'; export const runtime = 'edge'; @@ -10,22 +11,18 @@ export const runtime = 'edge'; const HISTORY_LIMIT = 20; /** - * GET /api/searchhistory?user= + * GET /api/searchhistory * 返回 string[] */ export async function GET(request: NextRequest) { try { - const { searchParams } = new URL(request.url); - const user = searchParams.get('user')?.trim(); - - if (!user) { - return NextResponse.json( - { error: 'User parameter is required' }, - { status: 400 } - ); + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const history = await db.getSearchHistory(user); + const history = await db.getSearchHistory(authInfo.username); return NextResponse.json(history, { status: 200 }); } catch (err) { console.error('获取搜索历史失败', err); @@ -38,13 +35,18 @@ export async function GET(request: NextRequest) { /** * POST /api/searchhistory - * body: { keyword: string, user: string } + * body: { keyword: string } */ export async function POST(request: NextRequest) { try { + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const body = await request.json(); const keyword: string = body.keyword?.trim(); - const user: string = body.user?.trim(); if (!keyword) { return NextResponse.json( @@ -53,17 +55,10 @@ export async function POST(request: NextRequest) { ); } - if (!user) { - return NextResponse.json( - { error: 'User parameter is required' }, - { status: 400 } - ); - } - - await db.addSearchHistory(user, keyword); + await db.addSearchHistory(authInfo.username, keyword); // 再次获取最新列表,确保客户端与服务端同步 - const history = await db.getSearchHistory(user); + const history = await db.getSearchHistory(authInfo.username); return NextResponse.json(history.slice(0, HISTORY_LIMIT), { status: 200 }); } catch (err) { console.error('添加搜索历史失败', err); @@ -75,25 +70,23 @@ export async function POST(request: NextRequest) { } /** - * DELETE /api/searchhistory?user=&keyword= + * DELETE /api/searchhistory?keyword= * * 1. 不带 keyword -> 清空全部搜索历史 * 2. 带 keyword= -> 删除单条关键字 */ export async function DELETE(request: NextRequest) { try { - const { searchParams } = new URL(request.url); - const user = searchParams.get('user')?.trim(); - const kw = searchParams.get('keyword')?.trim(); - - if (!user) { - return NextResponse.json( - { error: 'User parameter is required' }, - { status: 400 } - ); + // 从 cookie 获取用户信息 + const authInfo = getAuthInfoFromCookie(request); + if (!authInfo || !authInfo.username) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - await db.deleteSearchHistory(user, kw || undefined); + const { searchParams } = new URL(request.url); + const kw = searchParams.get('keyword')?.trim(); + + await db.deleteSearchHistory(authInfo.username, kw || undefined); return NextResponse.json({ success: true }, { status: 200 }); } catch (err) { diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 4fc0b3c..7065325 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -46,14 +46,6 @@ function LoginPageClient() { }); if (res.ok) { - // API 已经设置了认证cookie,这里只保存到localStorage用于向后兼容 - if (typeof window !== 'undefined') { - localStorage.setItem('password', password); - if (shouldAskUsername) { - localStorage.setItem('username', username); - } - } - const redirect = searchParams.get('redirect') || '/'; router.replace(redirect); } else if (res.status === 401) { @@ -83,12 +75,6 @@ function LoginPageClient() { }); if (res.ok) { - // API 已经设置了认证cookie,这里只保存到localStorage用于向后兼容 - if (typeof window !== 'undefined') { - localStorage.setItem('password', password); - localStorage.setItem('username', username); - } - const redirect = searchParams.get('redirect') || '/'; router.replace(redirect); } else { diff --git a/src/components/LogoutButton.tsx b/src/components/LogoutButton.tsx index 41c809c..3ca0b3f 100644 --- a/src/components/LogoutButton.tsx +++ b/src/components/LogoutButton.tsx @@ -23,12 +23,6 @@ export const LogoutButton: React.FC = () => { console.error('注销请求失败:', error); } - // 清除localStorage中的认证信息(向后兼容) - if (typeof window !== 'undefined') { - localStorage.removeItem('password'); - localStorage.removeItem('username'); - } - window.location.reload(); }; diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..b1cfa65 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,57 @@ +import { NextRequest } from 'next/server'; + +// 从cookie获取认证信息 (服务端使用) +export function getAuthInfoFromCookie(request: NextRequest): { + password?: string; + username?: string; + signature?: string; + timestamp?: number; +} | null { + const authCookie = request.cookies.get('auth'); + + if (!authCookie) { + return null; + } + + try { + const decoded = decodeURIComponent(authCookie.value); + const authData = JSON.parse(decoded); + return authData; + } catch (error) { + return null; + } +} + +// 从cookie获取认证信息 (客户端使用) +export function getAuthInfoFromBrowserCookie(): { + password?: string; + username?: string; + signature?: string; + timestamp?: number; +} | null { + if (typeof window === 'undefined') { + return null; + } + + try { + // 解析 document.cookie + const cookies = document.cookie.split(';').reduce((acc, cookie) => { + const [key, value] = cookie.trim().split('='); + if (key && value) { + acc[key] = value; + } + return acc; + }, {} as Record); + + const authCookie = cookies['auth']; + if (!authCookie) { + return null; + } + + const decoded = decodeURIComponent(authCookie); + const authData = JSON.parse(decoded); + return authData; + } catch (error) { + return null; + } +} diff --git a/src/lib/db.client.ts b/src/lib/db.client.ts index 90bd32c..63d82bf 100644 --- a/src/lib/db.client.ts +++ b/src/lib/db.client.ts @@ -24,27 +24,11 @@ export interface PlayRecord { play_time: number; // 播放进度(秒) total_time: number; // 总进度(秒) save_time: number; // 记录保存时间(时间戳) - user_id: number; // 用户 ID,本地存储情况下恒为 0 } // ---- 常量 ---- const PLAY_RECORDS_KEY = 'moontv_play_records'; -// +++ 新增:获取当前用户名工具函数 +++ -/** - * 从 localStorage 中读取当前用户名 - * 如果不存在则返回 undefined - */ -function getUsername(): string | undefined { - if (typeof window === 'undefined') return undefined; - try { - const name = localStorage.getItem('username')?.trim(); - return name || undefined; - } catch { - return undefined; - } -} - // ---- 环境变量 ---- const STORAGE_TYPE = (() => { const raw = @@ -84,10 +68,7 @@ export function generateStorageKey(source: string, id: string): string { export async function getAllPlayRecords(): Promise> { // 若配置标明使用数据库,则从后端 API 拉取 if (STORAGE_TYPE !== 'localstorage') { - const user = getUsername(); - return fetchFromApi>( - `/api/playrecords?user=${encodeURIComponent(user ?? '')}` - ); + return fetchFromApi>(`/api/playrecords`); } // 默认 / localstorage 流程 @@ -112,21 +93,19 @@ export async function getAllPlayRecords(): Promise> { export async function savePlayRecord( source: string, id: string, - record: Omit + record: PlayRecord ): Promise { const key = generateStorageKey(source, id); - const fullRecord: PlayRecord = { ...record, user_id: 0 }; // 若配置标明使用数据库,则通过 API 保存 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); const res = await fetch('/api/playrecords', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ user, key, record: fullRecord }), + body: JSON.stringify({ key, record }), }); if (!res.ok) throw new Error(`保存播放记录失败: ${res.status}`); } catch (err) { @@ -144,7 +123,7 @@ export async function savePlayRecord( try { const allRecords = await getAllPlayRecords(); - allRecords[key] = fullRecord; + allRecords[key] = record; localStorage.setItem(PLAY_RECORDS_KEY, JSON.stringify(allRecords)); } catch (err) { console.error('保存播放记录失败:', err); @@ -165,9 +144,7 @@ export async function deletePlayRecord( if (STORAGE_TYPE !== 'localstorage') { try { const res = await fetch( - `/api/playrecords?key=${encodeURIComponent( - key - )}&user=${encodeURIComponent(getUsername() ?? '')}`, + `/api/playrecords?key=${encodeURIComponent(key)}`, { method: 'DELETE', } @@ -206,10 +183,7 @@ export async function getSearchHistory(): Promise { // 如果配置为使用数据库,则从后端 API 获取 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); - return fetchFromApi( - `/api/searchhistory?user=${encodeURIComponent(user ?? '')}` - ); + return fetchFromApi(`/api/searchhistory`); } catch (err) { console.error('获取搜索历史失败:', err); return []; @@ -243,13 +217,12 @@ export async function addSearchHistory(keyword: string): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); await fetch('/api/searchhistory', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ keyword: trimmed, user: user ?? '' }), + body: JSON.stringify({ keyword: trimmed }), }); } catch (err) { console.error('保存搜索历史失败:', err); @@ -280,8 +253,7 @@ export async function clearSearchHistory(): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); - await fetch(`/api/searchhistory?user=${encodeURIComponent(user ?? '')}`, { + await fetch(`/api/searchhistory`, { method: 'DELETE', }); } catch (err) { @@ -305,15 +277,9 @@ export async function deleteSearchHistory(keyword: string): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); - await fetch( - `/api/searchhistory?user=${encodeURIComponent( - user ?? '' - )}&keyword=${encodeURIComponent(trimmed)}`, - { - method: 'DELETE', - } - ); + await fetch(`/api/searchhistory?keyword=${encodeURIComponent(trimmed)}`, { + method: 'DELETE', + }); } catch (err) { console.error('删除搜索历史失败:', err); } @@ -342,7 +308,6 @@ export interface Favorite { cover: string; total_episodes: number; save_time: number; - user_id: number; // 本地存储情况下恒为 0 } // 收藏在 localStorage 中使用的 key @@ -354,10 +319,7 @@ const FAVORITES_KEY = 'moontv_favorites'; export async function getAllFavorites(): Promise> { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { - const user = getUsername(); - return fetchFromApi>( - `/api/favorites?user=${encodeURIComponent(user ?? '')}` - ); + return fetchFromApi>(`/api/favorites`); } // localStorage 模式 @@ -381,21 +343,19 @@ export async function getAllFavorites(): Promise> { export async function saveFavorite( source: string, id: string, - favorite: Omit + favorite: Favorite ): Promise { const key = generateStorageKey(source, id); - const fullFavorite: Favorite = { ...favorite, user_id: 0 }; // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); const res = await fetch('/api/favorites', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ user, key, favorite: fullFavorite }), + body: JSON.stringify({ key, favorite }), }); if (!res.ok) throw new Error(`保存收藏失败: ${res.status}`); } catch (err) { @@ -413,7 +373,7 @@ export async function saveFavorite( try { const allFavorites = await getAllFavorites(); - allFavorites[key] = fullFavorite; + allFavorites[key] = favorite; localStorage.setItem(FAVORITES_KEY, JSON.stringify(allFavorites)); } catch (err) { console.error('保存收藏失败:', err); @@ -433,15 +393,9 @@ export async function deleteFavorite( // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); - const res = await fetch( - `/api/favorites?key=${encodeURIComponent( - key - )}&user=${encodeURIComponent(user ?? '')}`, - { - method: 'DELETE', - } - ); + const res = await fetch(`/api/favorites?key=${encodeURIComponent(key)}`, { + method: 'DELETE', + }); if (!res.ok) throw new Error(`删除收藏失败: ${res.status}`); } catch (err) { console.error('删除收藏到数据库失败:', err); @@ -478,12 +432,7 @@ export async function isFavorited( // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { try { - const user = getUsername(); - const res = await fetch( - `/api/favorites?key=${encodeURIComponent( - key - )}&user=${encodeURIComponent(user ?? '')}` - ); + const res = await fetch(`/api/favorites?key=${encodeURIComponent(key)}`); if (!res.ok) return false; const data = await res.json(); return !!data; @@ -505,7 +454,7 @@ export async function isFavorited( export async function toggleFavorite( source: string, id: string, - favoriteData?: Omit + favoriteData?: Favorite ): Promise { const already = await isFavorited(source, id); @@ -528,9 +477,8 @@ export async function toggleFavorite( export async function clearAllPlayRecords(): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { - const user = getUsername(); try { - await fetch(`/api/playrecords?user=${encodeURIComponent(user ?? '')}`, { + await fetch(`/api/playrecords`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, }); @@ -551,9 +499,8 @@ export async function clearAllPlayRecords(): Promise { export async function clearAllFavorites(): Promise { // 数据库模式 if (STORAGE_TYPE !== 'localstorage') { - const user = getUsername(); try { - await fetch(`/api/favorites?user=${encodeURIComponent(user ?? '')}`, { + await fetch(`/api/favorites`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, }); diff --git a/src/lib/db.ts b/src/lib/db.ts index 5f8f9fb..f20de74 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -63,8 +63,7 @@ export class DbManager { record: Omit ): Promise { const key = generateStorageKey(source, id); - const fullRecord: PlayRecord = { ...record, user_id: 0 }; - await this.storage.setPlayRecord(userName, key, fullRecord); + await this.storage.setPlayRecord(userName, key, record); } async getAllPlayRecords(userName: string): Promise<{ @@ -96,11 +95,10 @@ export class DbManager { userName: string, source: string, id: string, - favorite: Omit + favorite: Favorite ): Promise { const key = generateStorageKey(source, id); - const fullFavorite: Favorite = { ...favorite, user_id: 0 }; - await this.storage.setFavorite(userName, key, fullFavorite); + await this.storage.setFavorite(userName, key, favorite); } async getAllFavorites( diff --git a/src/lib/types.ts b/src/lib/types.ts index d69cf4c..aa4e726 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -10,7 +10,6 @@ export interface PlayRecord { play_time: number; // 播放进度(秒) total_time: number; // 总进度(秒) save_time: number; // 记录保存时间(时间戳) - user_id: number; // 用户ID,localStorage情况下全部为0 } // 收藏数据结构 @@ -19,7 +18,6 @@ export interface Favorite { total_episodes: number; // 总集数 title: string; cover: string; - user_id: number; // 用户ID,localStorage情况下全部为0 save_time: number; // 记录保存时间(时间戳) } diff --git a/src/middleware.ts b/src/middleware.ts index 5f97896..b71804a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,6 +2,8 @@ import { NextRequest, NextResponse } from 'next/server'; +import { getAuthInfoFromCookie } from '@/lib/auth'; + export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; @@ -94,28 +96,6 @@ async function verifySignature( } } -// 从cookie获取认证信息 -function getAuthInfoFromCookie(request: NextRequest): { - password?: string; - username?: string; - signature?: string; - timestamp?: number; -} | null { - const authCookie = request.cookies.get('auth'); - - if (!authCookie) { - return null; - } - - try { - const decoded = decodeURIComponent(authCookie.value); - const authData = JSON.parse(decoded); - return authData; - } catch (error) { - return null; - } -} - // 重定向到登录页面 function redirectToLogin(request: NextRequest, pathname: string): NextResponse { const loginUrl = new URL('/login', request.url);