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:
@@ -0,0 +1,61 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { prisma } from '../utils/prisma';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret';
|
||||
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user?: {
|
||||
id: string;
|
||||
plexId: string;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const authenticate = async (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
}
|
||||
|
||||
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 },
|
||||
select: { id: true, plexId: true, isAdmin: true, isActive: true }
|
||||
});
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
return res.status(401).json({ error: 'User not found or inactive' });
|
||||
}
|
||||
|
||||
req.user = {
|
||||
id: user.id,
|
||||
plexId: user.plexId,
|
||||
isAdmin: user.isAdmin
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: 'Invalid or expired token' });
|
||||
}
|
||||
};
|
||||
|
||||
export const requireAdmin = (
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
if (!req.user?.isAdmin) {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export interface ApiError extends Error {
|
||||
statusCode?: number;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export const errorHandler = (
|
||||
err: ApiError,
|
||||
_req: Request,
|
||||
res: Response,
|
||||
_next: NextFunction
|
||||
) => {
|
||||
console.error('Error:', err);
|
||||
|
||||
const statusCode = err.statusCode || 500;
|
||||
const message = err.message || 'Internal Server Error';
|
||||
|
||||
res.status(statusCode).json({
|
||||
error: {
|
||||
message,
|
||||
code: err.code || 'INTERNAL_ERROR',
|
||||
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const asyncHandler = (fn: Function) => {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user