186 lines
5.3 KiB
TypeScript
186 lines
5.3 KiB
TypeScript
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 };
|