增加求片功能

This commit is contained in:
mtvpls
2026-01-12 18:13:14 +08:00
parent 1053f2968e
commit 83b3315fba
9 changed files with 1049 additions and 20 deletions
+147
View File
@@ -0,0 +1,147 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db';
export const runtime = 'nodejs';
// GET: 获取单个求片详情
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const storage = getStorage();
const movieRequest = await storage.getMovieRequest(params.id);
if (!movieRequest) {
return NextResponse.json({ error: '求片不存在' }, { status: 404 });
}
return NextResponse.json({ request: movieRequest });
} catch (error) {
console.error('获取求片详情失败:', error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
}
}
// PATCH: 更新求片状态(标记已上架)
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const storage = getStorage();
const userInfo = await storage.getUserInfoV2(authInfo.username);
// 检查权限:只有管理员和站长可以操作
if (userInfo?.role !== 'admin' && userInfo?.role !== 'owner') {
return NextResponse.json({ error: '无权限操作' }, { status: 403 });
}
const body = await request.json();
const { status, fulfilledSource, fulfilledId } = body;
const movieRequest = await storage.getMovieRequest(params.id);
if (!movieRequest) {
return NextResponse.json({ error: '求片不存在' }, { status: 404 });
}
// 更新状态
const updates: any = {
status,
updatedAt: Date.now(),
};
if (status === 'fulfilled') {
updates.fulfilledAt = Date.now();
updates.fulfilledSource = fulfilledSource;
updates.fulfilledId = fulfilledId;
// 给所有求片用户发送通知
for (const username of movieRequest.requestedBy) {
await storage.addNotification(username, {
id: `req_fulfilled_${params.id}_${Date.now()}`,
type: 'request_fulfilled',
title: '求片已上架',
message: `您求的《${movieRequest.title}》已上架`,
timestamp: Date.now(),
read: false,
metadata: {
requestId: params.id,
source: fulfilledSource,
id: fulfilledId,
},
});
}
}
await storage.updateMovieRequest(params.id, updates);
return NextResponse.json({
message: '更新成功',
request: { ...movieRequest, ...updates },
});
} catch (error) {
console.error('更新求片失败:', error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
}
}
// DELETE: 删除求片
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const storage = getStorage();
const userInfo = await storage.getUserInfoV2(authInfo.username);
// 检查权限:只有管理员和站长可以删除
if (userInfo?.role !== 'admin' && userInfo?.role !== 'owner') {
return NextResponse.json({ error: '无权限操作' }, { status: 403 });
}
const movieRequest = await storage.getMovieRequest(params.id);
if (!movieRequest) {
return NextResponse.json({ error: '求片不存在' }, { status: 404 });
}
// 删除求片
await storage.deleteMovieRequest(params.id);
// 从所有用户的求片列表中移除
for (const username of movieRequest.requestedBy) {
await storage.removeUserMovieRequest(username, params.id);
}
return NextResponse.json({ message: '删除成功' });
} catch (error) {
console.error('删除求片失败:', error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
}
}
+200
View File
@@ -0,0 +1,200 @@
import { NextRequest, NextResponse } from 'next/server';
import { nanoid } from 'nanoid';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db';
import { MovieRequest } from '@/lib/types';
export const runtime = 'nodejs';
// GET: 获取求片列表
export async function GET(request: NextRequest) {
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const { searchParams } = new URL(request.url);
const status = searchParams.get('status') as 'pending' | 'fulfilled' | null;
const detail = searchParams.get('detail') !== 'false';
const myRequests = searchParams.get('my') === 'true';
const storage = getStorage();
if (myRequests) {
// 获取用户自己的求片
const requestIds = await storage.getUserMovieRequests(authInfo.username);
const requests = await Promise.all(
requestIds.map(id => storage.getMovieRequest(id))
);
const filtered = requests.filter(r => r !== null) as MovieRequest[];
return NextResponse.json({ requests: filtered });
}
// 获取所有求片
let requests = await storage.getAllMovieRequests();
// 按状态筛选
if (status) {
requests = requests.filter(r => r.status === status);
}
// 列表页不返回 requestedBy
if (!detail) {
requests = requests.map(r => ({ ...r, requestedBy: undefined }));
}
// 按求片人数和时间排序
requests.sort((a, b) => {
if (b.requestCount !== a.requestCount) {
return b.requestCount - a.requestCount;
}
return b.createdAt - a.createdAt;
});
return NextResponse.json({ requests });
} catch (error) {
console.error('获取求片列表失败:', error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
}
}
// POST: 创建或加入求片
export async function POST(request: NextRequest) {
const authInfo = getAuthInfoFromCookie(request);
if (!authInfo || !authInfo.username) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const body = await request.json();
const { tmdbId, title, year, mediaType, season, poster, overview } = body;
if (!title || !mediaType) {
return NextResponse.json({ error: '缺少必要参数' }, { status: 400 });
}
const storage = getStorage();
// 检查频率限制
const userInfo = await storage.getUserInfoV2(authInfo.username);
const rateLimit = parseInt(process.env.MOVIE_REQUEST_RATE_LIMIT || '3600') * 1000;
if (userInfo?.last_movie_request_time) {
const elapsed = Date.now() - userInfo.last_movie_request_time;
if (elapsed < rateLimit) {
const remaining = Math.ceil((rateLimit - elapsed) / 60000);
return NextResponse.json(
{ error: `操作太频繁,请${remaining}分钟后再试` },
{ status: 429 }
);
}
}
// 查重(剧集需要匹配季度)
const allRequests = await storage.getAllMovieRequests();
const existing = allRequests.find(r =>
(tmdbId && r.tmdbId === tmdbId && r.season === season) ||
(r.title === title && r.year === year && r.season === season)
);
if (existing) {
// 如果已上架,不允许再求
if (existing.status === 'fulfilled') {
return NextResponse.json({ error: '该影片已上架' }, { status: 400 });
}
// 检查用户是否已经求过
if (existing.requestedBy.includes(authInfo.username)) {
return NextResponse.json({ error: '您已经求过这部影片了' }, { status: 400 });
}
// 加入求片
existing.requestedBy.push(authInfo.username);
existing.requestCount++;
existing.updatedAt = Date.now();
await storage.updateMovieRequest(existing.id, existing);
await storage.addUserMovieRequest(authInfo.username, existing.id);
// 给站长发送通知
const ownerUsername = process.env.USERNAME;
if (ownerUsername) {
await storage.addNotification(ownerUsername, {
id: `movie_request_join_${existing.id}_${Date.now()}`,
type: 'movie_request',
title: '求片人数增加',
message: `${authInfo.username} 也想看:${existing.title}${existing.season ? `${existing.season}` : ''} (${existing.requestCount}人)`,
timestamp: Date.now(),
read: false,
metadata: {
requestId: existing.id,
username: authInfo.username,
},
});
}
return NextResponse.json({
message: '已加入求片',
request: existing
});
}
// 创建新求片
const newRequest: MovieRequest = {
id: nanoid(),
tmdbId,
title,
year,
mediaType,
season,
poster,
overview,
requestedBy: [authInfo.username],
requestCount: 1,
status: 'pending',
createdAt: Date.now(),
updatedAt: Date.now(),
};
await storage.createMovieRequest(newRequest);
await storage.addUserMovieRequest(authInfo.username, newRequest.id);
// 更新频率限制
await storage.setGlobalValue(
`user:${authInfo.username}:info:last_movie_request_time`,
Date.now().toString()
);
// 给站长发送通知
const ownerUsername = process.env.USERNAME;
if (ownerUsername) {
await storage.addNotification(ownerUsername, {
id: `movie_request_${newRequest.id}_${Date.now()}`,
type: 'movie_request',
title: '新求片请求',
message: `${authInfo.username} 求片:${title}${season ? `${season}` : ''}`,
timestamp: Date.now(),
read: false,
metadata: {
requestId: newRequest.id,
username: authInfo.username,
},
});
}
return NextResponse.json({
message: '求片成功',
request: newRequest
});
} catch (error) {
console.error('创建求片失败:', error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 }
);
}
}