← back to Dear Bubbe Nextjs
lib/feature-middleware.ts
146 lines
/**
* Feature Middleware for Bubbe.ai
*
* Intercepts premium feature requests and checks user access
* Works in parallel with existing Bubbe chat without disrupting it
*/
import { NextRequest, NextResponse } from 'next/server';
import { FeatureKey, detectRequestedFeatures } from './bubbe-pricing';
import { getUserIdFromRequest, canUserAccessFeature } from './user-session';
/**
* Detect if a chat message is requesting premium features
* and inject appropriate Bubbe responses if user lacks access
*/
export function checkChatFeatures(message: string, userId: string): {
hasRestriction: boolean;
bubbeResponse?: string;
blockedFeatures?: FeatureKey[];
} {
// Detect what features the user is trying to use
const requestedFeatures = detectRequestedFeatures(message);
if (requestedFeatures.length === 0) {
return { hasRestriction: false };
}
// Check each requested feature
const blockedFeatures: FeatureKey[] = [];
const responses: string[] = [];
for (const feature of requestedFeatures) {
const access = canUserAccessFeature(userId, feature);
if (!access.allowed) {
blockedFeatures.push(feature);
if (access.bubbeResponse) {
responses.push(access.bubbeResponse);
}
}
}
if (blockedFeatures.length > 0) {
return {
hasRestriction: true,
bubbeResponse: responses[0], // Use first rejection message
blockedFeatures
};
}
return { hasRestriction: false };
}
/**
* Middleware wrapper for API routes that require specific features
* Use this to protect premium endpoints
*/
export function requireFeature(feature: FeatureKey) {
return (handler: Function) => {
return async (req: NextRequest) => {
// Extract user ID from request
const userId = req.cookies.get('userId')?.value ||
req.headers.get('x-user-id') ||
`user_${req.headers.get('x-forwarded-for') || 'anonymous'}`;
// Check feature access
const access = canUserAccessFeature(userId, feature);
if (!access.allowed) {
return NextResponse.json({
error: 'feature_locked',
message: access.bubbeResponse,
requiredFeature: feature
}, { status: 402 }); // 402 Payment Required
}
// Feature allowed, proceed with handler
return handler(req);
};
};
}
/**
* Check if an API endpoint requires premium features
* based on the endpoint path
*/
export function getEndpointFeature(pathname: string): FeatureKey | null {
const featureMap: Record<string, FeatureKey> = {
'/api/bubbe-voice': 'voice_notes',
'/api/daily-reminder': 'daily_reminders',
'/api/holiday-mode': 'holiday_mode',
'/api/cooking-tips': 'cooking_tips',
'/api/family-gossip': 'family_gossip',
'/api/yiddish-full': 'yiddish_dictionary_full',
'/api/local-news': 'local_news',
'/api/local-sports': 'local_sports',
'/api/local-weather': 'local_weather',
'/api/personalized-insights': 'personalized_insights'
};
return featureMap[pathname] || null;
}
/**
* Inject feature check into existing chat response
* This modifies the response to include feature restrictions
*/
export function enhanceChatResponse(
originalResponse: string,
message: string,
userId: string
): string {
const featureCheck = checkChatFeatures(message, userId);
if (featureCheck.hasRestriction && featureCheck.bubbeResponse) {
// Prepend the feature restriction message to the original response
return featureCheck.bubbeResponse + "\n\n" +
"But here's what I CAN tell you for free:\n" +
originalResponse;
}
return originalResponse;
}
/**
* Log feature request attempts for analytics
*/
export function logFeatureAttempt(
userId: string,
feature: FeatureKey,
allowed: boolean
) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
userId,
feature,
allowed,
event: allowed ? 'feature_accessed' : 'feature_blocked'
};
// Log to console (in production, send to analytics)
console.log('[Feature Access]', JSON.stringify(logEntry));
// You could also write to a file or database here
return logEntry;
}