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>
15 KiB
Marketplace — Админ-панель, Модерация, Монетизация
Контекст
MVP маркетплейса полностью рабочий и задеплоен (buyer/seller flow, чат, офферы, нотификации, избранное, настройки). Но отсутствует вся бизнес-логика уровня продакшена: нет ролей, нет админки, нет модерации, нет системы жалоб, нет монетизации. Сейчас любой листинг публикуется мгновенно без проверки, нет возможности заблокировать пользователя со стороны платформы, и нет инструментов для управления маркетплейсом.
Сервер: 173.212.212.157 | Домен: marketplace.173.212.212.157.sslip.io
Фаза 1: Схема БД + RBAC (Роли и права)
Prisma Schema — server/prisma/schema.prisma
Новые enum'ы:
UserRole: USER, MODERATOR, ADMIN, SUPER_ADMINReportReason: SPAM, INAPPROPRIATE, SCAM, COUNTERFEIT, PROHIBITED_ITEM, HARASSMENT, OTHERReportStatus: OPEN, REVIEWING, RESOLVED, DISMISSEDReportTargetType: LISTING, USERSubscriptionTier: BASIC, PRO, BUSINESSSubscriptionStatus: ACTIVE, CANCELLED, EXPIRED, PAST_DUEPaymentType: LISTING_FEE, COMMISSION, PROMOTION, SUBSCRIPTIONModerationAction: APPROVED, REJECTED, WARNING, BAN, UNBAN, LISTING_DELETED, LISTING_FEATURED
Изменения существующих enum'ов:
ListingStatus: добавленPENDING_REVIEWмежду DRAFT и ACTIVENotificationType: добавленыLISTING_APPROVED, LISTING_REJECTED, MODERATION_WARNING, ACCOUNT_BANNED, ACCOUNT_UNBANNED, REPORT_RESOLVED
Изменения существующих моделей:
User: +role UserRole @default(USER),isBanned Boolean @default(false),banReason String?,bannedAt DateTime?,bannedBy String?Listing: +isFeatured Boolean @default(false),rejectionReason String?,reviewedBy String?,reviewedAt DateTime?Payment: +type PaymentType @default(LISTING_FEE),description String?
Новые модели (6 штук):
| Модель | Назначение | Ключевые поля |
|---|---|---|
Report |
Жалобы пользователей | reporterId, targetType, targetId, reason, status, resolvedBy, resolution |
PlatformConfig |
Настройки платформы (1 строка) | listingFee, commissionPercent, autoApprove, maxImagesPerListing, maxListingsFreeTier, proPrice, businessPrice, promotionDayPrice, blockedKeywords[] |
Subscription |
Подписки продавцов | userId (unique), tier, status, stripeSubscriptionId, currentPeriodEnd |
PromotedListing |
Продвинутые объявления | listingId (unique), userId, startDate, endDate, amountPaid, isActive |
ModerationLog |
Аудит-лог действий модераторов | moderatorId, targetUserId?, targetListingId?, action, reason, details(Json) |
Middleware — новые файлы
| Файл | Назначение |
|---|---|
server/src/middleware/requireRole.ts |
Фабрика middleware: requireRole('ADMIN', 'SUPER_ADMIN'). Проверяет роль из БД (не из JWT — изменения роли применяются мгновенно). Экспорт: requireModerator, requireAdmin, requireSuperAdmin |
server/src/middleware/checkBanned.ts |
Проверяет isBanned и возвращает 403 если забанен |
server/src/utils/moderation.ts |
Проверка текста на запрещённые слова из PlatformConfig. Кэш конфига на 60 сек |
Изменения существующих файлов
| Файл | Изменения |
|---|---|
server/src/middleware/auth.ts |
Расширен Request типом userRole?: string |
server/src/routes/auth.ts |
Добавлен role в select; проверка isBanned при логине (403 с причиной) |
client/src/types/index.ts |
Добавлен role: UserRole в User, новые типы Report, PlatformConfig, Subscription и т.д. |
client/src/context/AuthContext.tsx |
Добавлены isAdmin, isModerator, isSuperAdmin (computed из user.role) |
Seed — server/prisma/seed.ts
- Создана строка PlatformConfig с дефолтами
- alice → SUPER_ADMIN, bob → ADMIN, carol → MODERATOR
Фаза 2: Админ-панель — Layout + Dashboard
Новые компоненты
| Файл | Назначение |
|---|---|
client/src/components/layout/RequireRole.tsx |
Route guard: проверяет user.role, редирект на / если нет доступа |
client/src/components/layout/AdminLayout.tsx |
Sidebar с навигацией (Dashboard, Users, Listings, Reports, Moderation, Payments, Settings) + <Outlet /> |
client/src/components/ui/StatCard.tsx |
Карточка метрики: иконка, число, подпись, тренд |
client/src/components/ui/DataTable.tsx |
Переиспользуемая таблица: колонки, пагинация, поиск, сортировка |
Backend — server/src/routes/admin/stats.ts
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/stats |
MODERATOR+ | Общая статистика: юзеры, листинги, офферы, выручка, активные сегодня |
GET /api/admin/stats/revenue |
ADMIN+ | Выручка по дням/неделям/месяцам |
GET /api/admin/stats/users |
ADMIN+ | Рост пользователей по времени |
GET /api/admin/stats/listings |
MODERATOR+ | Активность листингов по времени |
Frontend — client/src/pages/admin/AdminDashboardPage.tsx
- 5 StatCard'ов: юзеры, листинги, офферы, выручка, активные
- Quick Stats и Platform Health панели
Router — client/src/router.tsx
/admin → RequireRole(['MODERATOR','ADMIN','SUPER_ADMIN']) → AdminLayout
/admin (index) → AdminDashboardPage
/admin/users → AdminUsersPage
/admin/users/:id → AdminUserDetailPage
/admin/listings → AdminListingsPage
/admin/reports → AdminReportsPage
/admin/moderation → AdminModerationPage
/admin/payments → AdminPaymentsPage
/admin/settings → AdminSettingsPage
Header — client/src/components/layout/Header.tsx
- Добавлена ссылка "Admin" в навигацию (если
isModerator || isAdmin)
Фаза 3: Управление пользователями
Backend — server/src/routes/admin/users.ts
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/users |
MODERATOR+ | Список юзеров с поиском, фильтром по роли/статусу, пагинацией |
GET /api/admin/users/:id |
MODERATOR+ | Полный профиль + кол-во листингов/офферов/жалоб + лог модерации |
PATCH /api/admin/users/:id/role |
SUPER_ADMIN | Сменить роль (нельзя себя понизить) |
POST /api/admin/users/:id/ban |
ADMIN+ | Забанить (body: { reason }). Создаёт ModerationLog, отправляет нотификацию ACCOUNT_BANNED |
POST /api/admin/users/:id/unban |
ADMIN+ | Разбанить. ModerationLog + нотификация ACCOUNT_UNBANNED |
Frontend
AdminUsersPage.tsx— DataTable: аватар+имя, email, роль (Badge), статус, дата регистрации, действияAdminUserDetailPage.tsx— профиль + статистика + история модерации + кнопки бан/разбан/роль
Фаза 4: Модерация объявлений
Backend
server/src/routes/admin/listings.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/listings |
MODERATOR+ | Все листинги, фильтр по статусу/категории, поиск |
POST /api/admin/listings/:id/approve |
MODERATOR+ | Статус → ACTIVE, ModerationLog, нотификация LISTING_APPROVED |
POST /api/admin/listings/:id/reject |
MODERATOR+ | Статус → DELETED, сохранить rejectionReason, нотификация LISTING_REJECTED |
DELETE /api/admin/listings/:id |
ADMIN+ | Принудительное удаление, ModerationLog |
POST /api/admin/listings/:id/feature |
ADMIN+ | Переключить isFeatured |
server/src/routes/admin/moderation.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/moderation/queue |
MODERATOR+ | Листинги со статусом PENDING_REVIEW, пагинация |
GET /api/admin/moderation/logs |
ADMIN+ | Полная история действий модерации |
Изменения server/src/routes/listing.ts:
POST /:id/activate: еслиPlatformConfig.autoApprove === false→ статусPENDING_REVIEWвместоACTIVEPOST /(создание): проверка текста наblockedKeywords→ если найдено, автоматическиPENDING_REVIEW- Добавлен
isFeaturedв listingSelect
Frontend
AdminListingsPage.tsx— DataTable с табами по статусу (ALL, PENDING_REVIEW, ACTIVE, SOLD, DELETED)AdminModerationPage.tsx— Карточки листингов на ревью: фото, заголовок, описание, продавец, кнопки Approve/Reject
Фаза 5: Система жалоб (Reports)
Backend
server/src/routes/report.ts (пользовательский):
| Endpoint | Доступ | Что делает |
|---|---|---|
POST /api/reports |
Авторизованный | Создать жалобу: { targetType, targetId, reason, description } |
server/src/routes/admin/reports.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/reports |
MODERATOR+ | Список жалоб, фильтр по статусу/типу, пагинация |
GET /api/admin/reports/:id |
MODERATOR+ | Детали жалобы с информацией о цели |
PATCH /api/admin/reports/:id |
MODERATOR+ | Разрешить/отклонить: { status, resolution } |
Frontend
ReportModal.tsx— Модалка с радиокнопками причин + описаниеProductDetailPage.tsx— кнопка "Report" теперь открывает ReportModal (вместоalert())AdminReportsPage.tsx— DataTable с табами по статусу
Фаза 6: Настройки платформы + Админ платежей
Backend
server/src/routes/admin/settings.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/settings |
ADMIN+ | Текущий PlatformConfig |
PATCH /api/admin/settings |
SUPER_ADMIN | Обновить любые поля PlatformConfig |
server/src/routes/admin/payments.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/admin/payments |
ADMIN+ | Все платежи, фильтр по типу/статусу, пагинация |
GET /api/admin/payments/revenue |
ADMIN+ | Разбивка: listing fees, commissions, promotions, subscriptions |
Изменения server/src/routes/payment.ts:
POST /create-intent: читаетlistingFeeиз PlatformConfig вместо хардкода500- Записывает
type: 'LISTING_FEE'в Payment
Frontend
AdminSettingsPage.tsx— Форма: listing fee, commission %, auto-approve toggle, max images, free tier limit, pro/business цены, promotion day price, blocked keywordsAdminPaymentsPage.tsx— 4 карточки выручки по типам + таблица транзакций
Фаза 7: Монетизация — Подписки + Продвижение
Backend
server/src/routes/subscription.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
GET /api/subscriptions/current |
Авторизованный | Текущая подписка (или BASIC по умолчанию) |
POST /api/subscriptions/create |
Авторизованный | Создать подписку PRO/BUSINESS |
POST /api/subscriptions/cancel |
Авторизованный | Отменить подписку |
server/src/routes/promotion.ts:
| Endpoint | Доступ | Что делает |
|---|---|---|
POST /api/listings/:id/promote |
Авторизованный | Оплатить продвижение (body: { days }) |
GET /api/listings/:id/promotion |
Авторизованный | Статус продвижения |
Изменения server/src/routes/offer.ts:
- При ACCEPTED: создаётся Payment с
type: 'COMMISSION'и суммой =commissionPercentот amount
Тарифные планы:
- Basic (бесплатно): до 5 листингов/мес
- Pro ($9.99/мес): безлимит + аналитика продавца
- Business ($29.99/мес): bulk-операции + приоритетная поддержка
Деплой
# 1. Миграция
cd server && npx prisma migrate dev --name admin_moderation_monetization
# 2. Обновить seed
npx tsx prisma/seed.ts
# 3. Билд и деплой
npm run build --workspace=client
scp -r client/dist/* root@173.212.212.157:/var/www/marketplace/
ssh root@173.212.212.157 'cd /var/www/marketplace-app && git pull && cd server && npm install && npx prisma db push && pm2 restart marketplace-api'
# 4. Seed на проде
ssh root@173.212.212.157 'cd /var/www/marketplace-app/server && npx tsx prisma/seed.ts'
Верификация
- Логин как alice (SUPER_ADMIN) → в хедере видна ссылка "Admin"
/admin→ дашборд с метриками/admin/users→ таблица юзеров, можно забанить david- Логин как david → получить 403 "Account suspended"
/admin/users→ разбанить david/admin/settings→ отключить autoApprove- Логин как david → создать листинг → статус PENDING_REVIEW
/admin/moderation→ увидеть листинг в очереди → Approve- На странице товара нажать Report → модалка → отправить жалобу
/admin/reports→ увидеть жалобу → Resolve/admin/payments→ все транзакции с типами/admin/settings→ изменить listing fee