import axios from "axios"; import { Router } from "express"; import { io } from "../index"; import { type AuthenticatedRequest, authenticate } from "../middleware/auth"; import { asyncHandler } from "../middleware/errorHandler"; import { prisma } from "../utils/prisma"; const router = Router(); const OVERSEER_URL = process.env.OVERSEER_URL || ""; const OVERSEER_API_KEY = process.env.OVERSEER_API_KEY || ""; const overseerClient = axios.create({ baseURL: `${OVERSEER_URL}/api/v1`, headers: { "X-Api-Key": OVERSEER_API_KEY }, }); router.get( "/costs", authenticate, asyncHandler(async (_req: AuthenticatedRequest, res) => { const settings = await prisma.systemSettings.findFirst(); res.json({ movie: settings?.movieRequestCost || 500, tv: settings?.tvRequestCost || 1000, tvPerSeason: settings?.tvPerSeasonCost || 250, }); }), ); router.get( "/balance", authenticate, asyncHandler(async (req: AuthenticatedRequest, res) => { const user = await prisma.user.findUnique({ where: { id: req.user!.id } }); if (!user) return res.json({ hasWallet: false, balance: 0, canRequestMovie: false, canRequestTV: false, }); const settings = await prisma.systemSettings.findFirst(); const dbBalance = user.totalEarned - user.totalSpent; res.json({ hasWallet: true, balance: dbBalance, canRequestMovie: dbBalance >= (settings?.movieRequestCost || 500), canRequestTV: dbBalance >= (settings?.tvRequestCost || 1000), costs: { movie: settings?.movieRequestCost || 500, tv: settings?.tvRequestCost || 1000, }, }); }), ); router.get( "/search", authenticate, asyncHandler(async (req: AuthenticatedRequest, res) => { const { query } = req.query; if (!query) return res.status(400).json({ error: "Query required" }); const response = await overseerClient.get("/search", { params: { query } }); res.json(response.data); }), ); router.post( "/request", authenticate, asyncHandler(async (req: AuthenticatedRequest, res) => { const { mediaType, mediaId, title, seasons } = req.body; if (!mediaType || !mediaId || !title) return res.status(400).json({ error: "Missing required fields" }); const user = await prisma.user.findUnique({ where: { id: req.user!.id } }); if (!user) return res.status(400).json({ error: "User required" }); const settings = await prisma.systemSettings.findFirst(); let cost = mediaType === "movie" ? settings?.movieRequestCost || 500 : settings?.tvRequestCost || 1000; if (mediaType === "tv" && seasons && seasons.length > 1) cost += (seasons.length - 1) * (settings?.tvPerSeasonCost || 250); const dbBalance = user.totalEarned - user.totalSpent; if (dbBalance < cost) return res .status(400) .json({ error: "Insufficient balance", required: cost, current: dbBalance, }); const overseerRequest = await overseerClient.post("/request", { mediaType, mediaId, ...(seasons && { seasons }), }); const request = await prisma.contentRequest.create({ data: { userId: user.id, overseerRequestId: overseerRequest.data.id, mediaType, tmdbId: mediaId, title, creditsCost: cost, status: "PENDING", requestedAt: new Date(), }, }); const transaction = await prisma.transaction.create({ data: { userId: user.id, type: "SPEND", amount: cost, requestId: request.id, description: `Request: ${title}`, contentTitle: title, }, }); await prisma.user.update({ where: { id: user.id }, data: { totalSpent: { increment: cost } }, }); io.to(`user:${user.id}`).emit("credits_spent", { amount: cost, title, transaction, }); res.json({ success: true, request, cost, message: `Request submitted. ${cost} $COOP deducted.`, }); }), ); router.post( "/webhook", asyncHandler(async (req, res) => { const rawId = req.body.request_id ?? req.body.requestId ?? req.body.id ?? req.body.overseer_request_id; const statusRaw = String(req.body.status || "").toUpperCase(); let status = "PENDING"; if (statusRaw === "APPROVED" || statusRaw === "COMPLETED") status = "APPROVED"; else if (statusRaw === "DECLINED" || statusRaw === "FAILED") status = "DECLINED"; if (!rawId) return res.status(400).json({ error: "Missing fields" }); const request = await prisma.contentRequest.findFirst({ where: { overseerRequestId: Number(rawId) }, include: { user: true }, }); if (!request) return res.status(404).json({ error: "Request not found" }); const updatedRequest = await prisma.contentRequest.update({ where: { id: request.id }, data: { status: status as any }, }); if (status === "DECLINED") { await prisma.transaction.create({ data: { userId: request.userId, type: "ADJUSTMENT", amount: request.creditsCost, description: `Refunded: ${request.title}`, contentTitle: request.title, }, }); await prisma.user.update({ where: { id: request.userId }, data: { totalSpent: { decrement: request.creditsCost } }, }); io.to(`user:${request.userId}`).emit("bonus_received", { amount: request.creditsCost, reason: `Refunded: ${request.title}`, }); } io.to(`user:${request.userId}`).emit("request_updated", { id: request.id, status: updatedRequest.status, title: request.title, }); res.json({ success: true, request: updatedRequest }); }), ); export { router as overseerRouter };