Files
mqsrv/src/routes/favorites.ts

187 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Router, Request, Response } from 'express';
import { z } from 'zod';
import { authMiddleware } from '../middleware/auth';
import { prisma } from '../utils/prisma';
const router = Router();
router.use(authMiddleware);
// source → targetType 映射
function sourceToType(source?: string): string {
if (source === '全面战场' || source === 'schemes_aob') return 'SCHEME_AOB';
return 'SCHEME'; // 烽火地带 / schemes
}
// 格式化方案卡片(对齐顶层 API
function formatScheme(s: any) {
return {
id: s.id,
user_id: s.userId,
description: s.description || '',
scheme_content: s.schemeContent || '',
category: s.category || '',
weapon_name: s.weaponName || '',
price: s.price ? `${s.price}W` : null,
tags: [],
uses: s.downloadsCount || 0,
status: 'normal',
comments: null,
shares: null,
created_at: s.createdAt?.toISOString?.() || s.createdAt,
updated_at: s.updatedAt?.toISOString?.() || s.updatedAt,
source: null,
total_historical_uses: s.downloadsCount || 0,
username: s.user?.username || '',
avatar: s.user?.avatar || '',
partner_type: null,
partner_level: null,
partner_badge: null,
partner_logo: null,
social_link: null,
likes: null,
};
}
// ============================================
// 获取收藏列表 (对齐 GET /api/favorites)
// ============================================
router.get('/', async (req: Request, res: Response) => {
try {
const { page = '1', limit = '12', sort = 'hot', weaponCategory, weaponName, search } = req.query;
const pageNum = Math.max(1, parseInt(String(page)) || 1);
const limitNum = Math.min(100, Math.max(1, parseInt(String(limit)) || 12));
// 先查用户的收藏记录
const total = await prisma.favorite.count({ where: { userId: req.user!.userId } });
const favorites = await prisma.favorite.findMany({
where: { userId: req.user!.userId },
orderBy: { createdAt: 'desc' },
skip: (pageNum - 1) * limitNum,
take: limitNum,
});
// 加载关联的方案数据
const data: any[] = [];
for (const fav of favorites) {
let scheme: any = null;
if (fav.targetType === 'SCHEME') {
scheme = await prisma.scheme.findUnique({
where: { id: fav.targetId },
include: { user: { select: { username: true, avatar: true } } },
});
} else if (fav.targetType === 'SCHEME_AOB') {
scheme = await prisma.schemeAob.findUnique({
where: { id: fav.targetId },
include: { user: { select: { username: true, avatar: true } } },
});
}
if (scheme && scheme.status === 'PUBLISHED') {
data.push(formatScheme(scheme));
}
}
res.json({
success: true,
data,
pagination: { page: pageNum, limit: limitNum, hasMore: pageNum * limitNum < total },
});
} catch (error) {
console.error('Get favorites error:', error);
res.status(500).json({ success: false, message: '获取失败' });
}
});
// ============================================
// 添加收藏 (对齐 POST /api/favorites)
// body: { schemeId, source: "烽火地带" }
// ============================================
const addFavoriteSchema = z.object({
schemeId: z.string().or(z.number()),
source: z.string().optional(),
});
router.post('/', async (req: Request, res: Response) => {
try {
const body = addFavoriteSchema.parse(req.body);
const targetType = sourceToType(body.source);
const targetId = String(body.schemeId);
const existing = await prisma.favorite.findFirst({
where: { userId: req.user!.userId, targetType, targetId },
});
if (existing) {
return res.json({ success: true, alreadyFavorited: true });
}
// 验证目标存在
let targetExists = false;
if (targetType === 'SCHEME') {
targetExists = !!(await prisma.scheme.findFirst({ where: { id: targetId, status: 'PUBLISHED' } }));
} else {
targetExists = !!(await prisma.schemeAob.findFirst({ where: { id: targetId, status: 'PUBLISHED' } }));
}
if (!targetExists) {
return res.status(404).json({ success: false, message: '方案不存在' });
}
await prisma.favorite.create({
data: { userId: req.user!.userId, targetType, targetId },
});
res.json({ success: true, alreadyFavorited: false });
} catch (error) {
console.error('Add favorite error:', error);
res.status(500).json({ success: false, message: '收藏失败' });
}
});
// ============================================
// 取消收藏 (对齐 DELETE /api/favorites/:schemeId?source=xxx)
// ============================================
router.delete('/:schemeId', async (req: Request, res: Response) => {
try {
const { schemeId } = req.params;
const source = req.query.source as string | undefined;
const targetType = sourceToType(source);
const favorite = await prisma.favorite.findFirst({
where: { userId: req.user!.userId, targetType, targetId: schemeId },
});
if (!favorite) {
return res.status(404).json({ success: false, message: '收藏不存在' });
}
await prisma.favorite.delete({ where: { id: favorite.id } });
res.json({ success: true });
} catch (error) {
console.error('Remove favorite error:', error);
res.status(500).json({ success: false, message: '取消收藏失败' });
}
});
// ============================================
// 检查收藏状态 (对齐 GET /api/favorites/check?schemeId=xx&source=xx)
// ============================================
router.get('/check', async (req: Request, res: Response) => {
try {
const { schemeId, source } = req.query;
if (!schemeId) {
return res.status(400).json({ success: false, message: '参数缺失' });
}
const targetType = sourceToType(source as string | undefined);
const favorite = await prisma.favorite.findFirst({
where: { userId: req.user!.userId, targetType, targetId: String(schemeId) },
});
res.json({ isFavorited: !!favorite });
} catch (error) {
console.error('Check favorite error:', error);
res.status(500).json({ success: false, message: '检查失败' });
}
});
export default router;