UNPKG

editia-core

Version:

Core services and utilities for Editia applications - Authentication, Monetization, Video Generation Types, and Database Management

336 lines 14.4 kB
"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.scriptConversationsMiddleware = exports.scriptGenerationMiddleware = exports.accountAnalysisMiddleware = exports.voiceCloneMiddleware = exports.sourceVideoUploadMiddleware = exports.videoGenerationMiddleware = exports.logMonetizationChecks = exports.addMonetizationHeaders = exports.createUsageIncrementMiddleware = exports.createMonetizationMiddleware = void 0; const monetization_1 = require("../../types/monetization"); // ============================================================================ // MIDDLEWARE FACTORY // ============================================================================ /** * Create monetization middleware for a specific feature */ function createMonetizationMiddleware(config) { return async (req, res, next) => { try { // Validate feature ID if (!(0, monetization_1.isValidFeatureId)(config.featureId)) { return res.status(400).json({ success: false, error: `Invalid feature ID: ${config.featureId}`, code: 'INVALID_FEATURE_ID', }); } // Validate action if provided if (config.action && !(0, monetization_1.isValidAction)(config.action)) { return res.status(400).json({ success: false, error: `Invalid action: ${config.action}`, code: 'INVALID_ACTION', }); } // 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_3.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(); } // Validate action if (!(0, monetization_1.isValidAction)(action)) { console.error(`Invalid action in usage increment middleware: ${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_3.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 // ============================================================================ const monetization_2 = require("../../types/monetization"); const monetization_3 = require("../../services/monetization"); /** * Middleware for video generation endpoint */ exports.videoGenerationMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.VIDEO_GENERATION, incrementUsage: true, action: monetization_2.ACTIONS.VIDEO_GENERATION, }); /** * Middleware for source video upload endpoint */ exports.sourceVideoUploadMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.SOURCE_VIDEOS, incrementUsage: true, action: monetization_2.ACTIONS.SOURCE_VIDEO_UPLOAD, }); /** * Middleware for voice cloning endpoint */ exports.voiceCloneMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.VOICE_CLONE, incrementUsage: true, action: monetization_2.ACTIONS.VOICE_CLONE, }); /** * Middleware for account analysis endpoint */ exports.accountAnalysisMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.ACCOUNT_ANALYSIS, incrementUsage: true, action: monetization_2.ACTIONS.ACCOUNT_ANALYSIS, }); /** * Middleware for script generation */ exports.scriptGenerationMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.SCRIPT_GENERATION, incrementUsage: true, action: monetization_2.ACTIONS.SCRIPT_CONVERSATIONS, }); /** * Middleware for script conversations */ exports.scriptConversationsMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.SCRIPT_CONVERSATIONS, incrementUsage: true, action: monetization_2.ACTIONS.SCRIPT_CONVERSATIONS, }); /** * Middleware for chat AI (no usage increment) */ exports.chatAiMiddleware = createMonetizationMiddleware({ featureId: monetization_2.FEATURES.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 = { [monetization_2.FEATURES.VIDEO_GENERATION]: { title: 'Video Generation Limit Reached', message: 'You have reached your video generation limit for this month.', upgradeMessage: 'Upgrade to generate more videos.', }, [monetization_2.FEATURES.SOURCE_VIDEOS]: { title: 'Source Video Upload Limit Reached', message: 'You have reached your source video upload limit.', upgradeMessage: 'Upgrade to upload more source videos.', }, [monetization_2.FEATURES.VOICE_CLONE]: { title: 'Voice Cloning Not Available', message: 'Voice cloning is not available in your current plan.', upgradeMessage: 'Upgrade to access voice cloning features.', }, [monetization_2.FEATURES.ACCOUNT_ANALYSIS]: { title: 'Account Analysis Limit Reached', message: 'You have reached your account analysis limit.', upgradeMessage: 'Upgrade for unlimited account analysis.', }, [monetization_2.FEATURES.SCRIPT_CONVERSATIONS]: { title: 'Script Conversations Limit Reached', message: 'You have reached your script conversations limit.', upgradeMessage: 'Upgrade for unlimited script conversations.', }, [monetization_2.FEATURES.SCRIPT_GENERATION]: { title: 'Script Generation Limit Reached', message: 'You have reached your script generation limit.', upgradeMessage: 'Upgrade for unlimited script generation.', }, [monetization_2.FEATURES.CHAT_AI]: { title: 'Chat AI Not Available', message: 'Chat AI is not available in your current plan.', upgradeMessage: 'Upgrade to access Chat AI features.', }, }; const featureId = result.details?.featureId || monetization_2.FEATURES.VIDEO_GENERATION; 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