5751e08eb1
Plex auth uses PIN-based flow, not standard OAuth2 authorization_code.
- backend/auth: POST /api/v2/pins to create PIN, GET /api/v2/pins/{id} to get authToken
- frontend/login: store pinId in sessionStorage before redirect
- frontend/callback: send pinId to backend instead of query code
- backend/index: fix dotenv path resolution for tsx (__dirname returns '.')
148 lines
3.5 KiB
TypeScript
148 lines
3.5 KiB
TypeScript
import express from "express";
|
|
import cors from "cors";
|
|
import helmet from "helmet";
|
|
import morgan from "morgan";
|
|
import rateLimit from "express-rate-limit";
|
|
import { createServer } from "http";
|
|
import { Server } from "socket.io";
|
|
import dotenv from "dotenv";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
const envPath =
|
|
["../.env", "../../.env", ".env"]
|
|
.map((p) => path.resolve(process.cwd(), p))
|
|
.find((p) => fs.existsSync(p)) || ".env";
|
|
|
|
dotenv.config({ path: envPath });
|
|
|
|
import { prisma } from "./utils/prisma";
|
|
import { errorHandler } from "./middleware/errorHandler";
|
|
import { authRouter } from "./routes/auth";
|
|
import { userRouter } from "./routes/users";
|
|
import { walletRouter } from "./routes/wallet";
|
|
import { transactionsRouter } from "./routes/transactions";
|
|
import { adminRouter } from "./routes/admin";
|
|
import { tautulliRouter } from "./routes/tautulli";
|
|
import { overseerRouter } from "./routes/overseer";
|
|
import { webhookRouter } from "./routes/webhooks";
|
|
import { setupSocketHandlers } from "./services/socket";
|
|
|
|
const app = express();
|
|
const httpServer = createServer(app);
|
|
const allowedOrigins = [
|
|
process.env.FRONTEND_URL,
|
|
"https://coop.hobokenchicken.com",
|
|
"http://172.20.1.238:3000",
|
|
].filter(Boolean) as string[];
|
|
|
|
const io = new Server(httpServer, {
|
|
cors: {
|
|
origin: allowedOrigins,
|
|
credentials: true,
|
|
},
|
|
});
|
|
|
|
// Security middleware
|
|
app.use(
|
|
helmet({
|
|
crossOriginResourcePolicy: { policy: "cross-origin" },
|
|
}),
|
|
);
|
|
app.use(
|
|
cors({
|
|
origin: allowedOrigins,
|
|
credentials: true,
|
|
}),
|
|
);
|
|
|
|
// Rate limiting
|
|
const limiter = rateLimit({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: 100, // limit each IP to 100 requests per windowMs
|
|
});
|
|
app.use(limiter);
|
|
|
|
// Body parsing
|
|
app.use(express.json({ limit: "10mb" }));
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Logging
|
|
app.use(morgan("combined"));
|
|
|
|
// Health check
|
|
app.get("/health", (_req, res) => {
|
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// API routes
|
|
app.use("/api/auth", authRouter);
|
|
app.use("/api/users", userRouter);
|
|
app.use("/api/wallet", walletRouter);
|
|
app.use("/api/transactions", transactionsRouter);
|
|
app.use("/api/admin", adminRouter);
|
|
app.use("/api/tautulli", tautulliRouter);
|
|
app.use("/api/overseer", overseerRouter);
|
|
app.use("/webhooks", webhookRouter);
|
|
|
|
// Error handling
|
|
app.use(errorHandler);
|
|
|
|
// Setup WebSocket handlers
|
|
setupSocketHandlers(io);
|
|
|
|
// Start server
|
|
const PORT = process.env.PORT || 3001;
|
|
|
|
async function startServer() {
|
|
try {
|
|
// Connect to database
|
|
await prisma.$connect();
|
|
console.log("✅ Connected to database");
|
|
|
|
// Ensure default settings exist
|
|
const settings = await prisma.systemSettings.findFirst();
|
|
if (!settings) {
|
|
await prisma.systemSettings.create({
|
|
data: {
|
|
id: "default",
|
|
},
|
|
});
|
|
console.log("✅ Created default system settings");
|
|
}
|
|
|
|
httpServer.listen(PORT, () => {
|
|
console.log(`🚀 Server running on port ${PORT}`);
|
|
console.log(
|
|
`📊 API URL: ${process.env.API_URL || `http://localhost:${PORT}`}`,
|
|
);
|
|
});
|
|
} catch (error) {
|
|
console.error("Failed to start server:", error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Graceful shutdown
|
|
process.on("SIGTERM", async () => {
|
|
console.log("SIGTERM received, shutting down gracefully");
|
|
await prisma.$disconnect();
|
|
httpServer.close(() => {
|
|
console.log("Server closed");
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
process.on("SIGINT", async () => {
|
|
console.log("SIGINT received, shutting down gracefully");
|
|
await prisma.$disconnect();
|
|
httpServer.close(() => {
|
|
console.log("Server closed");
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
startServer();
|
|
|
|
export { io };
|