Files
marketplace/server/src/routes/admin/reports.ts
delta-lynx-89e8 dbbbbd26f4 Add rental system: listings, bookings, payments, payouts, reviews
Full rental marketplace with 6 categories (apartment, house, car, motorcycle, bicycle, ebike).
Booking workflow: create → confirm → pay → active → complete → payout.
Landlord dashboard, admin moderation, availability calendar, Stripe Connect payouts.
14 QA bugs found and fixed including validator schemas, API response types, HTTP methods.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:33:29 -08:00

108 lines
3.0 KiB
TypeScript

import { Router } from 'express';
import { prisma } from '../../config/database.js';
import { requireModerator } from '../../middleware/requireRole.js';
import { validate } from '../../middleware/validate.js';
import { resolveReportSchema } from '../../validators/admin.js';
import { AppError } from '../../middleware/errorHandler.js';
const router = Router();
// GET /api/admin/reports
router.get('/', requireModerator, async (req, res, next) => {
try {
const { page = '1', pageSize = '20', status, targetType } = req.query;
const skip = (parseInt(page as string) - 1) * parseInt(pageSize as string);
const take = parseInt(pageSize as string);
const where: any = {};
if (status) where.status = status;
if (targetType) where.targetType = targetType;
const [reports, total] = await Promise.all([
prisma.report.findMany({
where,
include: {
reporter: { select: { id: true, fullName: true, avatar: true } },
},
skip,
take,
orderBy: { createdAt: 'desc' },
}),
prisma.report.count({ where }),
]);
res.json({
data: reports,
total,
page: parseInt(page as string),
pageSize: take,
totalPages: Math.ceil(total / take),
});
} catch (error) {
next(error);
}
});
// GET /api/admin/reports/:id
router.get('/:id', requireModerator, async (req, res, next) => {
try {
const report = await prisma.report.findUnique({
where: { id: req.params.id },
include: {
reporter: { select: { id: true, fullName: true, avatar: true, email: true } },
},
});
if (!report) throw new AppError(404, 'Report not found');
let target: any = null;
if (report.targetType === 'LISTING') {
target = await prisma.listing.findUnique({
where: { id: report.targetId },
select: { id: true, title: true, status: true, seller: { select: { id: true, fullName: true } } },
});
} else {
target = await prisma.user.findUnique({
where: { id: report.targetId },
select: { id: true, fullName: true, email: true, isBanned: true },
});
}
res.json({ report, target });
} catch (error) {
next(error);
}
});
// PATCH /api/admin/reports/:id
router.patch('/:id', requireModerator, validate(resolveReportSchema), async (req, res, next) => {
try {
const report = await prisma.report.findUnique({ where: { id: req.params.id } });
if (!report) throw new AppError(404, 'Report not found');
const updated = await prisma.report.update({
where: { id: req.params.id },
data: {
status: req.body.status,
resolution: req.body.resolution,
resolvedBy: req.userId,
},
});
await prisma.notification.create({
data: {
userId: report.reporterId,
type: 'REPORT_RESOLVED',
title: 'Report Updated',
body: `Your report has been ${req.body.status.toLowerCase()}.`,
data: { reportId: report.id },
},
});
res.json(updated);
} catch (error) {
next(error);
}
});
export default router;