editia-core
Version:
Core services and utilities for Editia applications - Authentication, Monetization, Video Generation Types, and Database Management
336 lines • 14.4 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.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