align API response format with maqt.top real specs: weapon-categories, weapons, schemes, schemes_aob, favorites check, encryption key derivation
This commit is contained in:
@@ -1,53 +1,89 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { authMiddleware, optionalAuth } from '../middleware/auth';
|
||||
import { encrypt } from '../utils/encryption';
|
||||
import { encrypt, encryptResponse } from '../utils/encryption';
|
||||
import { prisma } from '../utils/prisma';
|
||||
|
||||
const router = Router();
|
||||
|
||||
function formatScheme(s: any) {
|
||||
return {
|
||||
id: parseInt(s.id, 36) || String(s.id).split('-')[0] || s.id,
|
||||
user_id: s.userId,
|
||||
description: s.description || '',
|
||||
scheme_content: s.schemeContent || '',
|
||||
category: s.category || '',
|
||||
weapon_name: s.weaponName || '',
|
||||
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 = 20, weapon, category, sort = 'newest' } = req.query;
|
||||
|
||||
const {
|
||||
page = '1',
|
||||
limit = '12',
|
||||
weaponCategory,
|
||||
weaponName,
|
||||
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)) || 20));
|
||||
const limitNum = Math.min(100, Math.max(1, parseInt(String(limit)) || 12));
|
||||
const skip = (pageNum - 1) * limitNum;
|
||||
|
||||
|
||||
const where: any = { status: 'PUBLISHED' };
|
||||
if (weapon) where.weaponName = { contains: String(weapon) };
|
||||
if (category) where.category = String(category);
|
||||
|
||||
let orderBy: any = { createdAt: 'desc' };
|
||||
if (sort === 'popular') orderBy = { viewsCount: 'desc' };
|
||||
if (sort === 'downloads') orderBy = { downloadsCount: 'desc' };
|
||||
|
||||
if (weaponCategory) where.category = String(weaponCategory);
|
||||
if (weaponName) where.weaponName = { contains: String(weaponName) };
|
||||
if (search) where.description = { contains: String(search) };
|
||||
|
||||
let orderBy: any = { downloadsCount: 'desc' };
|
||||
if (sort === 'new') orderBy = { createdAt: 'desc' };
|
||||
|
||||
const schemes = await prisma.schemeAob.findMany({
|
||||
where,
|
||||
orderBy,
|
||||
skip,
|
||||
take: Number(limit),
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
weaponName: true,
|
||||
category: true,
|
||||
price: true,
|
||||
viewsCount: true,
|
||||
downloadsCount: true,
|
||||
likesCount: true,
|
||||
isOfficial: true,
|
||||
createdAt: true,
|
||||
user: {
|
||||
select: { id: true, username: true, avatar: true },
|
||||
},
|
||||
take: limitNum + 1,
|
||||
include: {
|
||||
user: { select: { id: true, username: true, avatar: true } },
|
||||
},
|
||||
});
|
||||
|
||||
res.json({ success: true, data: schemes });
|
||||
|
||||
const hasMore = schemes.length > limitNum;
|
||||
const data = schemes.slice(0, limitNum).map(formatScheme);
|
||||
|
||||
const payload = {
|
||||
success: true,
|
||||
data,
|
||||
pagination: { page: pageNum, limit: limitNum, hasMore },
|
||||
};
|
||||
|
||||
if (req.query.encrypted === '1') {
|
||||
res.json(encryptResponse(payload));
|
||||
} else {
|
||||
res.json(payload);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get schemes_aob error:', error);
|
||||
res.status(500).json({ success: false, message: '获取失败' });
|
||||
@@ -55,33 +91,36 @@ router.get('/', optionalAuth, async (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// 获取单个方案详情
|
||||
// 获取单个方案
|
||||
// ============================================
|
||||
router.get('/:id', optionalAuth, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const scheme = await prisma.schemeAob.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
user: { select: { id: true, username: true, avatar: true } },
|
||||
},
|
||||
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.schemeAob.update({
|
||||
where: { id },
|
||||
data: { viewsCount: { increment: 1 } },
|
||||
});
|
||||
|
||||
const encryptedContent = encrypt(scheme.schemeContent);
|
||||
|
||||
|
||||
let isFavorited = false;
|
||||
if (req.user) {
|
||||
const fav = await prisma.favorite.findFirst({
|
||||
where: { userId: req.user.userId, targetType: 'SCHEME_AOB', targetId: id },
|
||||
});
|
||||
isFavorited = !!fav;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: { ...scheme, schemeContent: encryptedContent },
|
||||
data: { ...formatScheme(scheme), scheme_content: encrypt(scheme.schemeContent), isFavorited },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Get scheme_aob error:', error);
|
||||
@@ -92,36 +131,37 @@ router.get('/:id', optionalAuth, async (req: Request, res: Response) => {
|
||||
// ============================================
|
||||
// 创建方案
|
||||
// ============================================
|
||||
const createSchema = z.object({
|
||||
description: z.string().min(1).max(50),
|
||||
category: z.string().optional(),
|
||||
weaponName: z.string().optional(),
|
||||
scheme: z.string().min(1),
|
||||
tags: z.array(z.string()).optional().default([]),
|
||||
});
|
||||
|
||||
router.post('/', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { title, description, weaponName, category, schemeContent, price = 0 } = req.body;
|
||||
|
||||
if (!title || !schemeContent) {
|
||||
return res.status(400).json({ success: false, message: '标题和内容不能为空' });
|
||||
}
|
||||
|
||||
const body = createSchema.parse(req.body);
|
||||
const scheme = await prisma.schemeAob.create({
|
||||
data: {
|
||||
userId: req.user!.userId,
|
||||
title,
|
||||
description,
|
||||
weaponName,
|
||||
category,
|
||||
schemeContent,
|
||||
price,
|
||||
description: body.description,
|
||||
weaponName: body.weaponName,
|
||||
category: body.category,
|
||||
schemeContent: body.scheme,
|
||||
status: 'PUBLISHED',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: { schemesCount: { increment: 1 } },
|
||||
});
|
||||
|
||||
res.json({ success: true, message: '方案创建成功', data: scheme });
|
||||
|
||||
res.json({ success: true, message: '方案发布成功', data: formatScheme(scheme) });
|
||||
} catch (error) {
|
||||
console.error('Create scheme_aob error:', error);
|
||||
res.status(500).json({ success: false, message: '创建失败' });
|
||||
res.status(500).json({ success: false, message: '发布失败' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,29 +171,12 @@ router.post('/', authMiddleware, async (req: Request, res: Response) => {
|
||||
router.delete('/:id', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const scheme = await prisma.schemeAob.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: '无权删除' });
|
||||
|
||||
const scheme = await prisma.schemeAob.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.schemeAob.update({
|
||||
where: { id },
|
||||
data: { status: 'DELETED' },
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: req.user!.userId },
|
||||
data: { schemesCount: { decrement: 1 } },
|
||||
});
|
||||
await prisma.schemeAob.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) {
|
||||
@@ -162,4 +185,46 @@ router.delete('/:id', authMiddleware, async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
// ============================================
|
||||
// 记录使用
|
||||
// ============================================
|
||||
router.post('/:id/use', optionalAuth, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const scheme = await prisma.schemeAob.findUnique({ where: { id }, select: { id: true } });
|
||||
if (!scheme) return res.status(404).json({ success: false, message: '方案不存在' });
|
||||
|
||||
await prisma.schemeAob.update({ where: { id }, data: { downloadsCount: { increment: 1 } } });
|
||||
|
||||
if (req.user) {
|
||||
await prisma.userLog.create({
|
||||
data: { userId: req.user.userId, action: 'SchemeAobUse', targetType: 'SchemeAob', targetId: id },
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ success: true, message: '使用次数已记录' });
|
||||
} catch (error) {
|
||||
console.error('Scheme AOB use error:', error);
|
||||
res.status(500).json({ success: false, message: '记录失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 举报
|
||||
router.post('/:id/report', authMiddleware, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const scheme = await prisma.schemeAob.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: 'SchemeAob', targetId: id },
|
||||
});
|
||||
|
||||
res.json({ success: true, message: '举报成功' });
|
||||
} catch (error) {
|
||||
console.error('Report scheme_aob error:', error);
|
||||
res.status(500).json({ success: false, message: '举报失败' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user