feat: add CoopCredits Solana media rewards ecosystem

Add complete  token system with Plex/Tautulli/Overseer integration:
- Anchor program for SPL token mint/burn/transfer
- Express backend with OAuth, webhooks, Solana integration
- Next.js frontend with dashboard, admin panel, wallet management
- Docker deployment for 172.20.1.0/24 infrastructure
- Production configs with SSL, Nginx, health monitoring

Tautulli webhooks auto-mint  on watch events.
Overseer integration burns  for content requests.
This commit is contained in:
2026-04-14 11:09:50 -04:00
parent f106328f6a
commit e8d9b1fd42
69 changed files with 11382 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
import { Router } from 'express';
import axios from 'axios';
import jwt from 'jsonwebtoken';
import { prisma } from '../utils/prisma';
import { asyncHandler } from '../middleware/errorHandler';
const router = Router();
// Plex OAuth configuration
const PLEX_CLIENT_ID = process.env.PLEX_CLIENT_ID || '';
const PLEX_REDIRECT_URI = process.env.PLEX_REDIRECT_URI || '';
const JWT_SECRET = process.env.JWT_SECRET || 'secret';
// Step 1: Get Plex OAuth URL
router.get('/plex/url', asyncHandler(async (_req, res) => {
const params = new URLSearchParams({
client_id: PLEX_CLIENT_ID,
redirect_uri: PLEX_REDIRECT_URI,
response_type: 'code',
scope: 'openid profile'
});
const authUrl = `https://app.plex.tv/auth#?${params.toString()}`;
res.json({ authUrl });
}));
// Step 2: Handle Plex OAuth callback
router.post('/plex/callback', asyncHandler(async (req, res) => {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: 'Authorization code required' });
}
// Exchange code for Plex token
const tokenResponse = await axios.post(
'https://plex.tv/api/v2/oauth/token',
{
code,
client_id: PLEX_CLIENT_ID,
grant_type: 'authorization_code'
},
{
headers: {
'Accept': 'application/json',
'X-Plex-Client-Identifier': PLEX_CLIENT_ID
}
}
);
const plexToken = tokenResponse.data.access_token;
// Get user info from Plex
const userResponse = await axios.get('https://plex.tv/api/v2/user', {
headers: {
'X-Plex-Token': plexToken,
'X-Plex-Client-Identifier': PLEX_CLIENT_ID,
'Accept': 'application/json'
}
});
const plexUser = userResponse.data;
// Check if user exists, create if not
let user = await prisma.user.findUnique({
where: { plexId: plexUser.id }
});
if (!user) {
user = await prisma.user.create({
data: {
plexId: plexUser.id,
plexUsername: plexUser.username || plexUser.email,
email: plexUser.email,
// Check if user is Plex admin (implement your logic)
isAdmin: false
}
});
} else {
// Update user info
user = await prisma.user.update({
where: { id: user.id },
data: {
plexUsername: plexUser.username || plexUser.email,
email: plexUser.email
}
});
}
// Create session
const session = await prisma.session.create({
data: {
userId: user.id,
token: jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' }),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
}
});
// Generate JWT
const token = jwt.sign(
{
userId: user.id,
plexId: user.plexId,
isAdmin: user.isAdmin
},
JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({
token,
sessionToken: session.token,
user: {
id: user.id,
plexId: user.plexId,
plexUsername: user.plexUsername,
email: user.email,
isAdmin: user.isAdmin,
walletAddress: user.walletAddress,
totalEarned: user.totalEarned,
totalSpent: user.totalSpent
}
});
}));
// Verify token
router.get('/verify', asyncHandler(async (req, res) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET) as any;
const user = await prisma.user.findUnique({
where: { id: decoded.userId }
});
if (!user || !user.isActive) {
return res.status(401).json({ error: 'User not found or inactive' });
}
res.json({
user: {
id: user.id,
plexId: user.plexId,
plexUsername: user.plexUsername,
email: user.email,
isAdmin: user.isAdmin,
walletAddress: user.walletAddress,
totalEarned: user.totalEarned,
totalSpent: user.totalSpent
}
});
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}));
// Logout
router.post('/logout', asyncHandler(async (req, res) => {
const authHeader = req.headers.authorization;
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, JWT_SECRET) as any;
await prisma.session.deleteMany({
where: { userId: decoded.userId }
});
} catch {
// Invalid token, ignore
}
}
res.json({ message: 'Logged out successfully' });
}));
export { router as authRouter };