803 lines
25 KiB
JavaScript
803 lines
25 KiB
JavaScript
const express = require("express")
|
|
const router = express.Router()
|
|
|
|
const requireAuth = require("../middleware/requireAuth")
|
|
const requireRole = require("../middleware/requireRole")
|
|
const { validate } = require("../middleware/validate.middleware")
|
|
const { endpoints } = require("@shared/contracts")
|
|
const {
|
|
getPendingDeals,
|
|
approveDeal,
|
|
rejectDeal,
|
|
expireDeal,
|
|
unexpireDeal,
|
|
getDealDetailForMod,
|
|
updateDealForMod,
|
|
} = require("../services/mod.service")
|
|
const { mapPaginatedDealsToDealCardResponse } = require("../adapters/responses/dealCard.adapter")
|
|
const { mapDealToDealDetailResponse } = require("../adapters/responses/dealDetail.adapter")
|
|
const dealReportService = require("../services/dealReport.service")
|
|
const badgeService = require("../services/badge.service")
|
|
const { setBadgeInRedis } = require("../services/redis/badgeCache.service")
|
|
const { attachTagsToDeal, removeTagsFromDeal, replaceTagsForDeal } = require("../services/tag.service")
|
|
const { updateDealInRedis, getOrCacheDealForModeration } = require("../services/redis/dealCache.service")
|
|
const { queueDealUpdate } = require("../services/redis/dbSync.service")
|
|
const moderationService = require("../services/moderation.service")
|
|
const adminService = require("../services/admin.service")
|
|
const adminMetricsService = require("../services/adminMetrics.service")
|
|
const { deleteCommentAsMod } = require("../services/comment.service")
|
|
const { enqueueAuditFromRequest, buildAuditMeta } = require("../services/audit.service")
|
|
const { AUDIT_ACTIONS } = require("../services/auditActions")
|
|
|
|
const { deals, mod } = endpoints
|
|
|
|
const listQueryValidator = validate(deals.dealsListRequestSchema, "query", "validatedDealListQuery")
|
|
const modUpdateValidator = validate(mod.modDealUpdateRequestSchema, "body", "validatedDealUpdate")
|
|
const modDealIdValidator = validate(mod.modDealUpdateParamsSchema, "params", "validatedDealId")
|
|
const modBadgeCreateValidator = validate(mod.modBadgeCreateRequestSchema, "body", "validatedBadgeCreate")
|
|
const modBadgeUpdateParamsValidator = validate(mod.modBadgeUpdateParamsSchema, "params", "validatedBadgeId")
|
|
const modBadgeUpdateValidator = validate(mod.modBadgeUpdateRequestSchema, "body", "validatedBadgeUpdate")
|
|
const modBadgeAssignValidator = validate(mod.modBadgeAssignRequestSchema, "body", "validatedBadgeAssign")
|
|
const modBadgeRemoveValidator = validate(mod.modBadgeRemoveRequestSchema, "body", "validatedBadgeRemove")
|
|
|
|
const buildViewer = (req) =>
|
|
req.auth ? { userId: req.auth.userId, role: req.auth.role } : null
|
|
|
|
const formatDateAsString = (value) =>
|
|
value instanceof Date ? value.toISOString() : value ?? null
|
|
|
|
function parseTagsFromBody(req, { allowEmpty = false } = {}) {
|
|
const tags = Array.isArray(req.body?.tags) ? req.body.tags : []
|
|
if (!allowEmpty && !tags.length) {
|
|
const err = new Error("Tag listesi gerekli")
|
|
err.statusCode = 400
|
|
throw err
|
|
}
|
|
return tags
|
|
}
|
|
|
|
const ALLOWED_DEAL_STATUSES = new Set(["PENDING", "ACTIVE", "REJECTED", "EXPIRED"])
|
|
|
|
function normalizeDealStatus(value) {
|
|
const normalized = String(value || "").trim().toUpperCase()
|
|
return ALLOWED_DEAL_STATUSES.has(normalized) ? normalized : null
|
|
}
|
|
|
|
router.get("/deals/pending", requireAuth, requireRole("MOD"), listQueryValidator, async (req, res) => {
|
|
try {
|
|
const { q, page, limit } = req.validatedDealListQuery
|
|
const payload = await getPendingDeals({
|
|
page,
|
|
limit,
|
|
filters: { ...req.query, q },
|
|
viewer: buildViewer(req),
|
|
})
|
|
|
|
const response = mapPaginatedDealsToDealCardResponse(payload)
|
|
res.json(response.results)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.post(
|
|
"/deals/:id/approve",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const updated = await approveDeal(id)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_APPROVE,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
after: { status: updated.status },
|
|
})
|
|
)
|
|
res.json({ id: updated.id, status: updated.status })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/deals/:id/reject",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const updated = await rejectDeal(id)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_REJECT,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
after: { status: updated.status },
|
|
})
|
|
)
|
|
res.json({ id: updated.id, status: updated.status })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/deals/:id/expire",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const updated = await expireDeal(id)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_EXPIRE,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
after: { status: updated.status },
|
|
})
|
|
)
|
|
res.json({ id: updated.id, status: updated.status })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/deals/:id/unexpire",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const updated = await unexpireDeal(id)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_UNEXPIRE,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
after: { status: updated.status },
|
|
})
|
|
)
|
|
res.json({ id: updated.id, status: updated.status })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.get(
|
|
"/deals/:id/detail",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const { deal, aiReview } = await getDealDetailForMod(id, buildViewer(req))
|
|
const mapped = mapDealToDealDetailResponse(deal)
|
|
const response = {
|
|
...mapped,
|
|
aiReview: aiReview
|
|
? {
|
|
dealId: aiReview.dealId,
|
|
bestCategoryId: aiReview.bestCategoryId,
|
|
categoryBreadcrumb: aiReview.categoryBreadcrumb || [],
|
|
needsReview: aiReview.needsReview,
|
|
hasIssue: aiReview.hasIssue,
|
|
issueType: aiReview.issueType,
|
|
issueReason: aiReview.issueReason ?? null,
|
|
tags: Array.isArray(aiReview.tags) ? aiReview.tags : [],
|
|
createdAt: formatDateAsString(aiReview.createdAt),
|
|
}
|
|
: null,
|
|
}
|
|
|
|
res.json(response)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.patch(
|
|
"/deals/:id",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
modDealIdValidator,
|
|
modUpdateValidator,
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const updated = await updateDealForMod(id, req.validatedDealUpdate, buildViewer(req))
|
|
const mapped = mapDealToDealDetailResponse(updated)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_UPDATE,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
extra: { fields: Object.keys(req.validatedDealUpdate || {}) },
|
|
})
|
|
)
|
|
res.json(mapped)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/deals/:id/tags",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const tags = parseTagsFromBody(req)
|
|
const result = await attachTagsToDeal(id, tags)
|
|
await updateDealInRedis(id, { tags: result.tags }, { updatedAt: new Date() })
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_TAG_ADD,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
after: { tags: result.tags },
|
|
})
|
|
)
|
|
res.json({ tags: result.tags })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.delete(
|
|
"/deals/:id/tags",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const tags = parseTagsFromBody(req)
|
|
const result = await removeTagsFromDeal(id, tags)
|
|
await updateDealInRedis(id, { tags: result.tags }, { updatedAt: new Date() })
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.DEAL_TAG_REMOVE,
|
|
buildAuditMeta({
|
|
entityType: "DEAL",
|
|
entityId: Number(id),
|
|
after: { tags: result.tags },
|
|
})
|
|
)
|
|
res.json({ tags: result.tags })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.put(
|
|
"/deals/:id/tags",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const tags = parseTagsFromBody(req, { allowEmpty: true })
|
|
const result = await replaceTagsForDeal(id, tags)
|
|
await updateDealInRedis(id, { tags: result.tags }, { updatedAt: new Date() })
|
|
res.json({ tags: result.tags })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.delete(
|
|
"/comments/:id",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedCommentId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedCommentId
|
|
const result = await deleteCommentAsMod(id)
|
|
res.json(result)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/users/:id/mute",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const durationDays = Number(req.body?.durationDays || 7)
|
|
const result = await moderationService.muteUser(id, { durationDays })
|
|
res.json({
|
|
userId: result.id,
|
|
mutedUntil: result.mutedUntil ? new Date(result.mutedUntil).toISOString() : null,
|
|
})
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.delete(
|
|
"/users/:id/mute",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const result = await moderationService.clearMute(id)
|
|
res.json({ userId: result.id, mutedUntil: null })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/users/:id/notes",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const note = String(req.body?.note || "").trim()
|
|
const result = await moderationService.addUserNote({
|
|
userId: id,
|
|
createdById: req.auth.userId,
|
|
note,
|
|
})
|
|
res.json(result)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.get(
|
|
"/users/:id/notes",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const page = Number(req.query.page || 1)
|
|
const limit = Number(req.query.limit || 20)
|
|
const result = await moderationService.listUserNotes({ userId: id, page, limit })
|
|
res.json(result)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.patch(
|
|
"/deals/reports/:id",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedReportId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedReportId
|
|
const status = req.body?.status
|
|
const result = await dealReportService.updateDealReportStatus({
|
|
reportId: id,
|
|
status,
|
|
})
|
|
res.json(result)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/users/:id/disable",
|
|
requireAuth,
|
|
requireRole("ADMIN"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const result = await moderationService.disableUser(id)
|
|
res.json({
|
|
userId: result.id,
|
|
disabledAt: result.disabledAt ? new Date(result.disabledAt).toISOString() : null,
|
|
})
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.delete(
|
|
"/users/:id/disable",
|
|
requireAuth,
|
|
requireRole("ADMIN"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const result = await moderationService.enableUser(id)
|
|
res.json({ userId: result.id, disabledAt: null })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.patch(
|
|
"/users/:id/role",
|
|
requireAuth,
|
|
requireRole("ADMIN"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedUserId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedUserId
|
|
const role = req.body?.role
|
|
if (String(role || "").toUpperCase() === "ADMIN") {
|
|
return res.status(400).json({ error: "ADMIN rolü verilemez" })
|
|
}
|
|
const result = await moderationService.updateUserRole(id, role)
|
|
res.json({ userId: result.id, role: result.role })
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post("/categories", requireAuth, requireRole("ADMIN"), async (req, res) => {
|
|
try {
|
|
const category = await adminService.createCategory(req.body || {})
|
|
res.json(category)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.patch(
|
|
"/categories/:id",
|
|
requireAuth,
|
|
requireRole("ADMIN"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedCategoryId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedCategoryId
|
|
const category = await adminService.updateCategory(id, req.body || {})
|
|
res.json(category)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post("/sellers", requireAuth, requireRole("ADMIN"), async (req, res) => {
|
|
try {
|
|
if (req.body?.id) {
|
|
const seller = await adminService.updateSeller(req.body.id, req.body || {}, {
|
|
createdById: req.auth.userId,
|
|
})
|
|
return res.json(seller)
|
|
}
|
|
const seller = await adminService.createSeller(req.body || {}, {
|
|
createdById: req.auth.userId,
|
|
})
|
|
res.json(seller)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.patch(
|
|
"/sellers/:id",
|
|
requireAuth,
|
|
requireRole("ADMIN"),
|
|
validate(mod.modDealUpdateParamsSchema, "params", "validatedSellerId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedSellerId
|
|
const seller = await adminService.updateSeller(id, req.body || {}, { createdById: req.auth.userId })
|
|
res.json(seller)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.get("/admin/categories", requireAuth, requireRole("ADMIN"), async (req, res) => {
|
|
try {
|
|
const categories = await adminService.listCategoriesCached()
|
|
res.json(categories)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.get("/admin/sellers", requireAuth, requireRole("ADMIN"), async (req, res) => {
|
|
try {
|
|
const sellers = await adminService.listSellersCached()
|
|
res.json(sellers)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.get("/admin/metrics", requireAuth, requireRole("ADMIN"), async (req, res) => {
|
|
try {
|
|
const metrics = await adminMetricsService.getAdminMetrics()
|
|
res.json(metrics)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.patch(
|
|
"/deals/:id/override",
|
|
requireAuth,
|
|
requireRole("ADMIN"),
|
|
validate(deals.dealDetailRequestSchema, "params", "validatedDealId"),
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedDealId
|
|
const status = req.body?.status
|
|
const userId = req.body?.userId
|
|
const normalizedStatus = status !== undefined ? normalizeDealStatus(status) : null
|
|
if (status !== undefined && !normalizedStatus) {
|
|
return res.status(400).json({ error: "Gecersiz status" })
|
|
}
|
|
const normalizedUserId =
|
|
userId !== undefined && userId !== null ? Number(userId) : undefined
|
|
if (normalizedUserId !== undefined) {
|
|
if (!Number.isInteger(normalizedUserId) || normalizedUserId <= 0) {
|
|
return res.status(400).json({ error: "Gecersiz userId" })
|
|
}
|
|
}
|
|
|
|
const { deal } = await getOrCacheDealForModeration(id)
|
|
if (!deal) return res.status(404).json({ error: "Deal bulunamadi" })
|
|
|
|
const patch = {}
|
|
if (status !== undefined) patch.status = normalizedStatus
|
|
if (normalizedUserId !== undefined) patch.userId = normalizedUserId
|
|
if (!Object.keys(patch).length) {
|
|
return res.status(400).json({ error: "Guncellenecek alan yok" })
|
|
}
|
|
|
|
const updatedAt = new Date()
|
|
const updated = await updateDealInRedis(id, patch, { updatedAt })
|
|
queueDealUpdate({
|
|
dealId: Number(id),
|
|
data: patch,
|
|
updatedAt: updatedAt.toISOString(),
|
|
}).catch((err) => console.error("DB sync deal override failed:", err?.message || err))
|
|
|
|
res.json({
|
|
id: Number(id),
|
|
status: updated?.status ?? patch.status ?? deal.status,
|
|
userId: updated?.userId ?? patch.userId ?? deal.userId,
|
|
})
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.get("/sellers", requireAuth, requireRole("MOD"), async (req, res) => {
|
|
try {
|
|
const sellers = await adminService.listSellersCached()
|
|
const payload = sellers.map((seller) => ({
|
|
id: seller.id,
|
|
name: seller.name,
|
|
url: seller.url ?? "",
|
|
sellerLogo: seller.sellerLogo ?? "",
|
|
isActive: seller.isActive ?? true,
|
|
}))
|
|
res.json(mod.modSellerListResponseSchema.parse(payload))
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.get("/categories", requireAuth, requireRole("MOD"), async (req, res) => {
|
|
try {
|
|
const categories = await adminService.listCategoriesCached()
|
|
const payload = categories.map((category) => ({
|
|
id: category.id,
|
|
name: category.name,
|
|
parentId: category.parentId ?? null,
|
|
}))
|
|
res.json(mod.modCategoryListResponseSchema.parse(payload))
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.get("/deals/reports", requireAuth, requireRole("MOD"), async (req, res) => {
|
|
try {
|
|
const payload = await dealReportService.listDealReports({
|
|
})
|
|
res.json(payload)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.get("/deals/reports/pending", requireAuth, requireRole("MOD"), async (req, res) => {
|
|
try {
|
|
const page = Number(req.query.page || 1)
|
|
const payload = await dealReportService.getPendingReports({ page })
|
|
res.json(payload)
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.post("/badges", requireAuth, requireRole("MOD"), modBadgeCreateValidator, async (req, res) => {
|
|
try {
|
|
const badge = await badgeService.createBadge(req.validatedBadgeCreate)
|
|
await setBadgeInRedis(badge)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.BADGE_CREATE,
|
|
buildAuditMeta({
|
|
entityType: "BADGE",
|
|
entityId: badge.id,
|
|
after: { name: badge.name },
|
|
})
|
|
)
|
|
res.json(mod.modBadgeResponseSchema.parse(badge))
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
})
|
|
|
|
router.patch(
|
|
"/badges/:id",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
modBadgeUpdateParamsValidator,
|
|
modBadgeUpdateValidator,
|
|
async (req, res) => {
|
|
try {
|
|
const { id } = req.validatedBadgeId
|
|
const badge = await badgeService.updateBadge(id, req.validatedBadgeUpdate)
|
|
await setBadgeInRedis(badge)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.BADGE_UPDATE,
|
|
buildAuditMeta({
|
|
entityType: "BADGE",
|
|
entityId: badge.id,
|
|
extra: { fields: Object.keys(req.validatedBadgeUpdate || {}) },
|
|
})
|
|
)
|
|
res.json(mod.modBadgeResponseSchema.parse(badge))
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.post(
|
|
"/badges/assign",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
modBadgeAssignValidator,
|
|
async (req, res) => {
|
|
try {
|
|
const assigned = await badgeService.assignBadgeToUser(req.validatedBadgeAssign)
|
|
const response = {
|
|
userId: assigned.userId,
|
|
badgeId: assigned.badgeId,
|
|
earnedAt: assigned.earnedAt instanceof Date ? assigned.earnedAt.toISOString() : assigned.earnedAt,
|
|
}
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.BADGE_ASSIGN,
|
|
buildAuditMeta({
|
|
entityType: "USER",
|
|
entityId: response.userId,
|
|
extra: { badgeId: response.badgeId, earnedAt: response.earnedAt },
|
|
})
|
|
)
|
|
res.json(mod.modBadgeAssignResponseSchema.parse(response))
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
router.delete(
|
|
"/badges/assign",
|
|
requireAuth,
|
|
requireRole("MOD"),
|
|
modBadgeRemoveValidator,
|
|
async (req, res) => {
|
|
try {
|
|
await badgeService.removeBadgeFromUser(req.validatedBadgeRemove)
|
|
enqueueAuditFromRequest(
|
|
req,
|
|
AUDIT_ACTIONS.MOD.BADGE_REMOVE,
|
|
buildAuditMeta({
|
|
entityType: "USER",
|
|
entityId: req.validatedBadgeRemove.userId,
|
|
extra: { badgeId: req.validatedBadgeRemove.badgeId },
|
|
})
|
|
)
|
|
res.json(mod.modBadgeRemoveResponseSchema.parse({ removed: true }))
|
|
} catch (err) {
|
|
const status = err.statusCode || 500
|
|
res.status(status).json({ error: err.message || "Sunucu hatasi" })
|
|
}
|
|
}
|
|
)
|
|
|
|
module.exports = router
|