import { Router, Request, Response } from 'express'; import { z } from 'zod'; import { authMiddleware, optionalAuth } from '../middleware/auth'; import { encrypt, encryptResponse } from '../utils/encryption'; import { prisma } from '../utils/prisma'; const router = Router(); // ---- 响应 shape 对齐 maqt.top 原版 ---- 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: [], likes: s.likesCount ?? null, uses: s.downloadsCount || 0, status: s.status === 'PUBLISHED' ? 'normal' : null, comments: null, shares: null, created_at: s.createdAt?.toISOString?.() || s.createdAt, updated_at: s.updatedAt?.toISOString?.() || s.updatedAt, source: s.source ?? null, total_historical_uses: s.downloadsCount || 0, username: s.user?.username || s.username || '', avatar: s.user?.avatar || s.avatar || 'https://tuku.maqt.top/i/2026/03/22/sv3fg9.png', partner_type: null, partner_level: null, partner_badge: null, partner_logo: null, social_link: null, }; } // ============================================ // 获取方案列表 // ============================================ router.get('/', optionalAuth, async (req: Request, res: Response) => { try { const { page = '1', limit = '12', weaponCategory, weaponName, minPrice, maxPrice, search, sort = 'hot', } = req.query; const pageNum = Math.max(1, parseInt(String(page)) || 1); const limitNum = Math.min(100, Math.max(1, parseInt(String(limit)) || 12)); const skip = (pageNum - 1) * limitNum; const where: any = { status: 'PUBLISHED' }; if (weaponCategory) where.category = String(weaponCategory); if (weaponName) where.weaponName = { contains: String(weaponName) }; if (minPrice) where.price = { ...(where.price || {}), gte: parseInt(String(minPrice)) }; if (maxPrice) where.price = { ...(where.price || {}), lte: parseInt(String(maxPrice)) }; if (search) where.description = { contains: String(search) }; let orderBy: any = { downloadsCount: 'desc' }; if (sort === 'new') orderBy = { createdAt: 'desc' }; const schemes = await prisma.scheme.findMany({ where, orderBy, skip, take: limitNum + 1, // fetch one extra to detect hasMore include: { user: { select: { id: true, username: true, avatar: true } }, }, }); const hasMore = schemes.length > limitNum; const data = schemes.slice(0, limitNum).map(formatScheme); const payload = { success: true, data, pagination: { page: pageNum, limit: limitNum, hasMore }, }; // 支持加密输出 const useEncryption = req.query.encrypted === '1'; if (useEncryption) { res.json(encryptResponse(payload)); } else { res.json(payload); } } catch (error) { console.error('Get schemes error:', error); res.status(500).json({ success: false, message: '获取失败' }); } }); // ============================================ // 获取单个方案详情 // ============================================ router.get('/:id', optionalAuth, async (req: Request, res: Response) => { try { const { id } = req.params; const scheme = await prisma.scheme.findUnique({ where: { id }, include: { user: { select: { id: true, username: true, avatar: true }, }, }, }); if (!scheme || (scheme.status !== 'PUBLISHED' && scheme.userId !== req.user?.userId)) { return res.status(404).json({ success: false, message: '方案不存在' }); } await prisma.scheme.update({ where: { id }, data: { viewsCount: { increment: 1 } }, }); let isFavorited = false; if (req.user) { const fav = await prisma.favorite.findFirst({ where: { userId: req.user.userId, targetType: 'SCHEME', targetId: id }, }); isFavorited = !!fav; } const encryptedContent = encrypt(scheme.schemeContent); res.json({ success: true, data: { ...formatScheme(scheme), scheme_content: encryptedContent, isFavorited, }, }); } catch (error) { console.error('Get scheme error:', error); res.status(500).json({ success: false, message: '获取失败' }); } }); // ============================================ // 创建方案 // ============================================ const createSchemeSchema = z.object({ description: z.string().min(1).max(50), category: z.string().optional(), weaponName: z.string().optional(), scheme: z.string().min(1), price: z.union([z.number(), z.string()]).optional(), tags: z.array(z.string()).optional().default([]), }); router.post('/', authMiddleware, async (req: Request, res: Response) => { try { const body = createSchemeSchema.parse(req.body); // 支持 x-user-info header (Base64 JSON {id, username}) const userInfoHeader = req.headers['x-user-info'] as string | undefined; let userId = req.user!.userId; if (userInfoHeader) { try { const decoded = JSON.parse(Buffer.from(userInfoHeader, 'base64').toString('utf-8')); if (decoded.id && decoded.username) { // 用 header 中的 id 查找对应本地用户 const localUser = await prisma.user.findUnique({ where: { username: decoded.username } }); if (localUser) userId = localUser.id; } } catch { /* ignore */ } } const priceNum = typeof body.price === 'string' ? parseInt(body.price.replace(/[W万]/g, '')) : (body.price || 0); const scheme = await prisma.scheme.create({ data: { userId, description: body.description, weaponName: body.weaponName, category: body.category, schemeContent: body.scheme, price: priceNum, status: 'PUBLISHED', }, include: { user: { select: { id: true, username: true, avatar: true } }, }, }); await prisma.user.update({ where: { id: userId }, data: { schemesCount: { increment: 1 } }, }); res.json({ success: true, message: '方案发布成功', data: formatScheme(scheme) }); } catch (error) { console.error('Create scheme error:', error); res.status(500).json({ success: false, message: '发布失败' }); } }); // ============================================ // 删除方案 // ============================================ router.delete('/:id', authMiddleware, async (req: Request, res: Response) => { try { const { id } = req.params; const scheme = await prisma.scheme.findUnique({ where: { id }, select: { userId: true } }); if (!scheme) return res.status(404).json({ success: false, message: '方案不存在' }); if (scheme.userId !== req.user!.userId) return res.status(403).json({ success: false, message: '无权删除' }); await prisma.scheme.update({ where: { id }, data: { status: 'DELETED' } }); await prisma.user.update({ where: { id: req.user!.userId }, data: { schemesCount: { decrement: 1 } } }); res.json({ success: true, message: '方案已删除' }); } catch (error) { console.error('Delete scheme error:', error); res.status(500).json({ success: false, message: '删除失败' }); } }); // ============================================ // 记录使用 (POST /api/schemes/:id/use) // ============================================ router.post('/:id/use', optionalAuth, async (req: Request, res: Response) => { try { const { id } = req.params; const scheme = await prisma.scheme.findUnique({ where: { id }, select: { id: true } }); if (!scheme) return res.status(404).json({ success: false, message: '方案不存在' }); const updated = await prisma.scheme.update({ where: { id }, data: { downloadsCount: { increment: 1 } }, select: { downloadsCount: true }, }); if (req.user) { await prisma.userLog.create({ data: { userId: req.user.userId, action: 'SchemeUse', targetType: 'Scheme', targetId: id }, }); } res.json({ success: true, message: '使用次数已记录', downloadsCount: updated.downloadsCount }); } catch (error) { console.error('Scheme use error:', error); res.status(500).json({ success: false, message: '记录失败' }); } }); // ============================================ // 举报 (POST /api/schemes/:id/report) // ============================================ router.post('/:id/report', authMiddleware, async (req: Request, res: Response) => { try { const { id } = req.params; const { reason, description } = req.body; const scheme = await prisma.scheme.findUnique({ where: { id }, select: { id: true } }); if (!scheme) return res.status(404).json({ success: false, message: '方案不存在' }); await prisma.userLog.create({ data: { userId: req.user!.userId, action: 'Report', targetType: 'Scheme', targetId: id, }, }); res.json({ success: true, message: '举报成功' }); } catch (error) { console.error('Report error:', error); res.status(500).json({ success: false, message: '举报失败' }); } }); export default router;