editia-core
Version:
Core services and utilities for Editia applications - Authentication, Monetization, Video Generation Types, and Database Management
289 lines • 11.9 kB
JavaScript
"use strict";
/**
* Monetization Middleware for Express
*
* This middleware integrates with the MonetizationService to protect
* endpoints based on user subscription and usage limits.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.userFriendlyMonetizationErrorHandler = exports.defaultMonetizationErrorHandler = exports.chatAiMiddleware = exports.scriptGenerationMiddleware = exports.accountAnalysisMiddleware = exports.voiceCloneMiddleware = exports.sourceVideoUploadMiddleware = exports.videoGenerationMiddleware = exports.logMonetizationChecks = exports.addMonetizationHeaders = exports.createUsageIncrementMiddleware = exports.createMonetizationMiddleware = void 0;
const monetization_service_1 = require("../services/monetization/monetization-service");
// ============================================================================
// MIDDLEWARE FACTORY
// ============================================================================
/**
* Create monetization middleware for a specific feature
*/
function createMonetizationMiddleware(config) {
return async (req, res, next) => {
try {
// Get user ID from request (assuming it's set by auth middleware)
const userId = req.user?.id || req.userId;
if (!userId) {
return res.status(401).json({
success: false,
error: 'User not authenticated',
code: 'AUTHENTICATION_REQUIRED',
});
}
// Get monetization service instance
const monetizationService = monetization_service_1.MonetizationService.getInstance();
// Check monetization access
const result = await monetizationService.checkMonetization(userId, config.featureId);
// Store monetization info in request for later use
req.monetization = {
hasAccess: result.hasAccess,
currentPlan: result.currentPlan,
remainingUsage: result.remainingUsage,
totalLimit: result.totalLimit,
featureId: config.featureId,
};
// If access denied, return error
if (!result.success) {
if (config.errorHandler) {
return config.errorHandler(req, res, result);
}
return res.status(403).json({
success: false,
error: result.error,
code: result.hasAccess ? 'USAGE_LIMIT_REACHED' : 'FEATURE_ACCESS_DENIED',
details: {
featureId: config.featureId,
requiredPlan: result.details?.requiredPlan,
currentPlan: result.currentPlan,
remainingUsage: result.remainingUsage,
totalLimit: result.totalLimit,
},
upgrade: !result.hasAccess ? {
requiredPlan: result.details?.requiredPlan,
currentPlan: result.currentPlan,
} : undefined,
});
}
// If we should increment usage after successful operation
if (config.incrementUsage && config.action) {
// Store the action to increment later (after successful operation)
req.monetizationAction = config.action;
}
next();
}
catch (error) {
console.error('Monetization middleware error:', error);
return res.status(500).json({
success: false,
error: 'Internal server error',
code: 'MONETIZATION_SERVICE_ERROR',
});
}
};
}
exports.createMonetizationMiddleware = createMonetizationMiddleware;
// ============================================================================
// USAGE INCREMENTATION MIDDLEWARE
// ============================================================================
/**
* Middleware to increment usage after successful operation
* This should be placed AFTER the main operation middleware
*/
function createUsageIncrementMiddleware() {
return async (req, res, next) => {
const originalSend = res.send;
const action = req.monetizationAction;
if (!action) {
return next();
}
// Override res.send to intercept the response
res.send = function (body) {
try {
const responseBody = typeof body === 'string' ? JSON.parse(body) : body;
// Only increment usage if the operation was successful
if (responseBody.success !== false && res.statusCode < 400) {
const userId = req.user?.id || req.userId;
const monetizationService = monetization_service_1.MonetizationService.getInstance();
// Increment usage asynchronously (don't wait for it)
monetizationService.incrementUsage(userId, action).catch(error => {
console.error('Error incrementing usage:', error);
});
}
}
catch (error) {
console.error('Error in usage increment middleware:', error);
}
// Call original send
return originalSend.call(this, body);
};
next();
};
}
exports.createUsageIncrementMiddleware = createUsageIncrementMiddleware;
// ============================================================================
// HELPER MIDDLEWARE
// ============================================================================
/**
* Middleware to add monetization info to response headers
*/
function addMonetizationHeaders() {
return (req, res, next) => {
if (req.monetization) {
res.set({
'X-Monetization-HasAccess': req.monetization.hasAccess.toString(),
'X-Monetization-CurrentPlan': req.monetization.currentPlan,
'X-Monetization-RemainingUsage': req.monetization.remainingUsage.toString(),
'X-Monetization-TotalLimit': req.monetization.totalLimit.toString(),
'X-Monetization-FeatureId': req.monetization.featureId,
});
}
next();
};
}
exports.addMonetizationHeaders = addMonetizationHeaders;
/**
* Middleware to log monetization checks (development only)
*/
function logMonetizationChecks() {
return (req, res, next) => {
if (req.monetization) {
console.log('🔒 Monetization Check:', {
userId: req.user?.id || req.userId,
featureId: req.monetization.featureId,
hasAccess: req.monetization.hasAccess,
currentPlan: req.monetization.currentPlan,
remainingUsage: req.monetization.remainingUsage,
totalLimit: req.monetization.totalLimit,
path: req.path,
method: req.method,
});
}
next();
};
}
exports.logMonetizationChecks = logMonetizationChecks;
// ============================================================================
// PRESET MIDDLEWARE FOR COMMON FEATURES
// ============================================================================
/**
* Middleware for video generation endpoint
*/
exports.videoGenerationMiddleware = createMonetizationMiddleware({
featureId: 'video_generation',
incrementUsage: true,
action: 'video_generation',
});
/**
* Middleware for source video upload endpoint
*/
exports.sourceVideoUploadMiddleware = createMonetizationMiddleware({
featureId: 'source_videos',
incrementUsage: true,
action: 'source_video_upload',
});
/**
* Middleware for voice cloning endpoint
*/
exports.voiceCloneMiddleware = createMonetizationMiddleware({
featureId: 'voice_clone',
incrementUsage: true,
action: 'voice_clone',
});
/**
* Middleware for account analysis endpoint
*/
exports.accountAnalysisMiddleware = createMonetizationMiddleware({
featureId: 'account_analysis',
incrementUsage: true,
action: 'account_analysis',
});
/**
* Middleware for script generation (no usage increment)
*/
exports.scriptGenerationMiddleware = createMonetizationMiddleware({
featureId: 'script_generation',
incrementUsage: false,
});
/**
* Middleware for chat AI (no usage increment)
*/
exports.chatAiMiddleware = createMonetizationMiddleware({
featureId: 'chat_ai',
incrementUsage: false,
});
// ============================================================================
// ERROR HANDLERS
// ============================================================================
/**
* Default error handler for monetization failures
*/
function defaultMonetizationErrorHandler(req, res, result) {
const errorResponse = {
success: false,
error: result.error,
code: result.hasAccess ? 'USAGE_LIMIT_REACHED' : 'FEATURE_ACCESS_DENIED',
details: {
featureId: result.details?.featureId,
requiredPlan: result.details?.requiredPlan,
currentPlan: result.currentPlan,
remainingUsage: result.remainingUsage,
totalLimit: result.totalLimit,
},
};
// Add upgrade information if access is denied
if (!result.hasAccess) {
errorResponse.upgrade = {
requiredPlan: result.details?.requiredPlan,
currentPlan: result.currentPlan,
message: `This feature requires a ${result.details?.requiredPlan} plan. You currently have a ${result.currentPlan} plan.`,
};
}
return res.status(403).json(errorResponse);
}
exports.defaultMonetizationErrorHandler = defaultMonetizationErrorHandler;
/**
* Custom error handler that returns a more user-friendly response
*/
function userFriendlyMonetizationErrorHandler(req, res, result) {
const messages = {
'video_generation': {
title: 'Video Generation Limit Reached',
message: 'You have reached your video generation limit for this month.',
upgradeMessage: 'Upgrade to generate more videos.',
},
'source_videos': {
title: 'Source Video Upload Limit Reached',
message: 'You have reached your source video upload limit.',
upgradeMessage: 'Upgrade to upload more source videos.',
},
'voice_clone': {
title: 'Voice Cloning Not Available',
message: 'Voice cloning is not available in your current plan.',
upgradeMessage: 'Upgrade to access voice cloning features.',
},
'account_analysis': {
title: 'Account Analysis Limit Reached',
message: 'You have reached your account analysis limit.',
upgradeMessage: 'Upgrade for unlimited account analysis.',
},
};
const featureId = result.details?.featureId || 'unknown';
const featureMessages = messages[featureId] || {
title: 'Feature Access Denied',
message: 'This feature is not available in your current plan.',
upgradeMessage: 'Upgrade to access this feature.',
};
return res.status(403).json({
success: false,
error: featureMessages.message,
title: featureMessages.title,
code: result.hasAccess ? 'USAGE_LIMIT_REACHED' : 'FEATURE_ACCESS_DENIED',
upgrade: !result.hasAccess ? {
requiredPlan: result.details?.requiredPlan,
currentPlan: result.currentPlan,
message: featureMessages.upgradeMessage,
} : undefined,
limits: {
remaining: result.remainingUsage,
total: result.totalLimit,
},
});
}
exports.userFriendlyMonetizationErrorHandler = userFriendlyMonetizationErrorHandler;
//# sourceMappingURL=monetization-middleware.js.map