mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-05-17 19:27:29 +08:00
fix: search history isolation
This commit is contained in:
@@ -10,12 +10,22 @@ export const runtime = 'edge';
|
|||||||
const HISTORY_LIMIT = 20;
|
const HISTORY_LIMIT = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/searchhistory
|
* GET /api/searchhistory?user=<username>
|
||||||
* 返回 string[]
|
* 返回 string[]
|
||||||
*/
|
*/
|
||||||
export async function GET() {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const history = await db.getSearchHistory();
|
const { searchParams } = new URL(request.url);
|
||||||
|
const user = searchParams.get('user')?.trim();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'User parameter is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const history = await db.getSearchHistory(user);
|
||||||
return NextResponse.json(history, { status: 200 });
|
return NextResponse.json(history, { status: 200 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取搜索历史失败', err);
|
console.error('获取搜索历史失败', err);
|
||||||
@@ -28,12 +38,14 @@ export async function GET() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/searchhistory
|
* POST /api/searchhistory
|
||||||
* body: { keyword: string }
|
* body: { keyword: string, user: string }
|
||||||
*/
|
*/
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const keyword: string = body.keyword?.trim();
|
const keyword: string = body.keyword?.trim();
|
||||||
|
const user: string = body.user?.trim();
|
||||||
|
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Keyword is required' },
|
{ error: 'Keyword is required' },
|
||||||
@@ -41,10 +53,17 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.addSearchHistory(keyword);
|
if (!user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'User parameter is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.addSearchHistory(user, keyword);
|
||||||
|
|
||||||
// 再次获取最新列表,确保客户端与服务端同步
|
// 再次获取最新列表,确保客户端与服务端同步
|
||||||
const history = await db.getSearchHistory();
|
const history = await db.getSearchHistory(user);
|
||||||
return NextResponse.json(history.slice(0, HISTORY_LIMIT), { status: 200 });
|
return NextResponse.json(history.slice(0, HISTORY_LIMIT), { status: 200 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('添加搜索历史失败', err);
|
console.error('添加搜索历史失败', err);
|
||||||
@@ -56,7 +75,7 @@ export async function POST(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE /api/searchhistory
|
* DELETE /api/searchhistory?user=<username>&keyword=<kw>
|
||||||
*
|
*
|
||||||
* 1. 不带 keyword -> 清空全部搜索历史
|
* 1. 不带 keyword -> 清空全部搜索历史
|
||||||
* 2. 带 keyword=<kw> -> 删除单条关键字
|
* 2. 带 keyword=<kw> -> 删除单条关键字
|
||||||
@@ -64,9 +83,17 @@ export async function POST(request: NextRequest) {
|
|||||||
export async function DELETE(request: NextRequest) {
|
export async function DELETE(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
|
const user = searchParams.get('user')?.trim();
|
||||||
const kw = searchParams.get('keyword')?.trim();
|
const kw = searchParams.get('keyword')?.trim();
|
||||||
|
|
||||||
await db.deleteSearchHistory(kw || undefined);
|
if (!user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'User parameter is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.deleteSearchHistory(user, kw || undefined);
|
||||||
|
|
||||||
return NextResponse.json({ success: true }, { status: 200 });
|
return NextResponse.json({ success: true }, { status: 200 });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -206,7 +206,10 @@ export async function getSearchHistory(): Promise<string[]> {
|
|||||||
// 如果配置为使用数据库,则从后端 API 获取
|
// 如果配置为使用数据库,则从后端 API 获取
|
||||||
if (STORAGE_TYPE !== 'localstorage') {
|
if (STORAGE_TYPE !== 'localstorage') {
|
||||||
try {
|
try {
|
||||||
return fetchFromApi<string[]>('/api/searchhistory');
|
const user = getUsername();
|
||||||
|
return fetchFromApi<string[]>(
|
||||||
|
`/api/searchhistory?user=${encodeURIComponent(user ?? '')}`
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取搜索历史失败:', err);
|
console.error('获取搜索历史失败:', err);
|
||||||
return [];
|
return [];
|
||||||
@@ -240,12 +243,13 @@ export async function addSearchHistory(keyword: string): Promise<void> {
|
|||||||
// 数据库模式
|
// 数据库模式
|
||||||
if (STORAGE_TYPE !== 'localstorage') {
|
if (STORAGE_TYPE !== 'localstorage') {
|
||||||
try {
|
try {
|
||||||
|
const user = getUsername();
|
||||||
await fetch('/api/searchhistory', {
|
await fetch('/api/searchhistory', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ keyword: trimmed }),
|
body: JSON.stringify({ keyword: trimmed, user: user ?? '' }),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('保存搜索历史失败:', err);
|
console.error('保存搜索历史失败:', err);
|
||||||
@@ -276,7 +280,8 @@ export async function clearSearchHistory(): Promise<void> {
|
|||||||
// 数据库模式
|
// 数据库模式
|
||||||
if (STORAGE_TYPE !== 'localstorage') {
|
if (STORAGE_TYPE !== 'localstorage') {
|
||||||
try {
|
try {
|
||||||
await fetch('/api/searchhistory', {
|
const user = getUsername();
|
||||||
|
await fetch(`/api/searchhistory?user=${encodeURIComponent(user ?? '')}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -300,9 +305,15 @@ export async function deleteSearchHistory(keyword: string): Promise<void> {
|
|||||||
// 数据库模式
|
// 数据库模式
|
||||||
if (STORAGE_TYPE !== 'localstorage') {
|
if (STORAGE_TYPE !== 'localstorage') {
|
||||||
try {
|
try {
|
||||||
await fetch(`/api/searchhistory?keyword=${encodeURIComponent(trimmed)}`, {
|
const user = getUsername();
|
||||||
method: 'DELETE',
|
await fetch(
|
||||||
});
|
`/api/searchhistory?user=${encodeURIComponent(
|
||||||
|
user ?? ''
|
||||||
|
)}&keyword=${encodeURIComponent(trimmed)}`,
|
||||||
|
{
|
||||||
|
method: 'DELETE',
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('删除搜索历史失败:', err);
|
console.error('删除搜索历史失败:', err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ export interface IStorage {
|
|||||||
checkUserExist(userName: string): Promise<boolean>;
|
checkUserExist(userName: string): Promise<boolean>;
|
||||||
|
|
||||||
// 搜索历史相关
|
// 搜索历史相关
|
||||||
getSearchHistory(): Promise<string[]>;
|
getSearchHistory(userName: string): Promise<string[]>;
|
||||||
addSearchHistory(keyword: string): Promise<void>;
|
addSearchHistory(userName: string, keyword: string): Promise<void>;
|
||||||
deleteSearchHistory(keyword?: string): Promise<void>;
|
deleteSearchHistory(userName: string, keyword?: string): Promise<void>;
|
||||||
|
|
||||||
// 用户列表
|
// 用户列表
|
||||||
getAllUsers(): Promise<string[]>;
|
getAllUsers(): Promise<string[]>;
|
||||||
@@ -184,26 +184,30 @@ class RedisStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 搜索历史 ----------
|
// ---------- 搜索历史 ----------
|
||||||
private shKey = 'moontv:search_history';
|
private shKey(user: string) {
|
||||||
|
return `u:${user}:sh`; // u:username:sh
|
||||||
async getSearchHistory(): Promise<string[]> {
|
|
||||||
return (await this.client.lRange(this.shKey, 0, -1)) as string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addSearchHistory(keyword: string): Promise<void> {
|
async getSearchHistory(userName: string): Promise<string[]> {
|
||||||
|
return (await this.client.lRange(this.shKey(userName), 0, -1)) as string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSearchHistory(userName: string, keyword: string): Promise<void> {
|
||||||
|
const key = this.shKey(userName);
|
||||||
// 先去重
|
// 先去重
|
||||||
await this.client.lRem(this.shKey, 0, keyword);
|
await this.client.lRem(key, 0, keyword);
|
||||||
// 插入到最前
|
// 插入到最前
|
||||||
await this.client.lPush(this.shKey, keyword);
|
await this.client.lPush(key, keyword);
|
||||||
// 限制最大长度
|
// 限制最大长度
|
||||||
await this.client.lTrim(this.shKey, 0, SEARCH_HISTORY_LIMIT - 1);
|
await this.client.lTrim(key, 0, SEARCH_HISTORY_LIMIT - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSearchHistory(keyword?: string): Promise<void> {
|
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
|
||||||
|
const key = this.shKey(userName);
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
await this.client.lRem(this.shKey, 0, keyword);
|
await this.client.lRem(key, 0, keyword);
|
||||||
} else {
|
} else {
|
||||||
await this.client.del(this.shKey);
|
await this.client.del(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,16 +375,16 @@ export class DbManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 搜索历史 ----------
|
// ---------- 搜索历史 ----------
|
||||||
async getSearchHistory(): Promise<string[]> {
|
async getSearchHistory(userName: string): Promise<string[]> {
|
||||||
return this.storage.getSearchHistory();
|
return this.storage.getSearchHistory(userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addSearchHistory(keyword: string): Promise<void> {
|
async addSearchHistory(userName: string, keyword: string): Promise<void> {
|
||||||
await this.storage.addSearchHistory(keyword);
|
await this.storage.addSearchHistory(userName, keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSearchHistory(keyword?: string): Promise<void> {
|
async deleteSearchHistory(userName: string, keyword?: string): Promise<void> {
|
||||||
await this.storage.deleteSearchHistory(keyword);
|
await this.storage.deleteSearchHistory(userName, keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取全部用户名
|
// 获取全部用户名
|
||||||
|
|||||||
Reference in New Issue
Block a user