feat: admin api cookie

This commit is contained in:
shinya
2025-07-09 22:42:53 +08:00
parent 7edc673c9f
commit 2bbb67ac9c
16 changed files with 215 additions and 384 deletions

View File

@@ -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();

View File

@@ -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 });
}

View File

@@ -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 });
}
}
// 更新缓存中的站点设置

View File

@@ -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<string, any>;
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) {

View File

@@ -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 }
);
}
}
// 查找目标用户条目

View File

@@ -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<Favorite, 'user_id'>;
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);
})
);
}

View File

@@ -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);
})
);
}

View File

@@ -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=<username>
* 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=<username>&keyword=<kw>
* DELETE /api/searchhistory?keyword=<kw>
*
* 1. 不带 keyword -> 清空全部搜索历史
* 2. 带 keyword=<kw> -> 删除单条关键字
*/
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) {